Zulip Chat Archive

Stream: general

Topic: Determining variable binders from syntax


Michael Rothgang (Jul 31 2024 at 16:20):

Given a variable command, can I determine the inner binder names from a syntax linter? I can get a syntax node of explicitBinder kind, but don't see how to parse out the binder names...

I tried something like the following (minimised):

import Batteries.Tactic.Lint
open Lean Elab Command

def badVariableLinter : Linter where run := withSetOptionIn fun stx  do
    if ( MonadState.get).messages.hasErrors then
      return
    if let Syntax.node _ ``Lean.Parser.Command.variable args := stx then
      let binders := (args.map getBinders).flatten
      if #[``Lean.Parser.Term.implicitBinder, ``Lean.Parser.Term.explicitBinder].contains kind then
        binders.push stx else fargs
      for b in binders do
        -- How to get the list of binder names in b?

More generally, (how) could I obtain the parsed binders from Term.explicitBinder or Term.implicitBinder? If I just print b, the information is there, but I'm missing how to extract it...

Context: I'm trying to write a syntax linter for variable commands which update how a variable is bound and declare new variables at the same time.

Michael Rothgang (Jul 31 2024 at 16:22):

More generally: is there a way to print the actual syntax for some Lean code? Using something like `Linter.logLint linter.badVariable stx s!"arguments are {args}" pretty-prints the syntax; I would like to see the internal structure.
@Damiano Testa You mentioned some inspect command a few times. Where can I find that? (The API docs don't mention any such method.)

Michael Rothgang (Jul 31 2024 at 16:38):

@Damiano Testa It seems you tried something similar in #14675, but switched to a different approach? Did you manage to solve the problem I'm worrying about?

Damiano Testa (Jul 31 2024 at 17:08):

Can you give an example of what you would like? E.g. with

variable (a : Nat) {b c : Int}

you would like a, b, c? Or explicit, implicit, implicit?

Damiano Testa (Jul 31 2024 at 17:19):

Michael Rothgang said:

Damiano Testa It seems you tried something similar in #14675, but switched to a different approach? Did you manage to solve the problem I'm worrying about?

I simply started with something else, since I wanted to wait for the new variable mechanism before continuing.

I would still like to make "dependent" typeclass variables flagged by a linter, but I was not sure whether the adaptation to the new variable command and this linter would have stepped on each other's toes (and I guess that they would!).

Damiano Testa (Jul 31 2024 at 17:20):

Michael Rothgang said:

Damiano Testa You mentioned some inspect command a few times. Where can I find that? (The API docs don't mention any such method.)

inspect is a command that I defined to print the tree structure of some syntax. Using dbg_trace stx gives you some idea of what the tree structure looks like.

Damiano Testa (Jul 31 2024 at 17:21):

Johan suggested upgrading inspect to a clickable interface and PRing it, but I have not gotten around to it.

Michael Rothgang (Jul 31 2024 at 17:24):

Damiano Testa said:

Can you give an example of what you would like? E.g. with

variable (a : Nat) {b c : Int}

you would like a, b, c? Or explicit, implicit, implicit?

Ultimately, I am interested in a list of items (name, withType): for variable (a) (b : Nat) this would say (a, false), (b, true).

Michael Rothgang (Jul 31 2024 at 17:25):

FWIW, I asked about this linter and the new variables command - and my impression was that this linter is still desirable.

Damiano Testa (Jul 31 2024 at 17:33):

Is this close to what you wanted?

import Lean

open Lean

def getNamesWithType (stx : Syntax) : Array (Syntax × Bool) :=
  if #[``Lean.Parser.Term.implicitBinder, ``Lean.Parser.Term.explicitBinder].contains stx.getKind then
    stx[1].getArgs.map (·, stx[2][0] != default)
  else #[]

run_cmd
  let stxs := [ `(bracketedBinder| (a : Nat)),  `(bracketedBinder| (b c)),  `(bracketedBinder| {d e f})]
  for stx in stxs do
    logInfo m!"{getNamesWithType stx.raw}"

Damiano Testa (Jul 31 2024 at 17:34):

Michael Rothgang said:

FWIW, I asked about this linter and the new variables command - and my impression was that this linter is still desirable.

I was thinking of a different linter: the one that I had in mind would complain if you have [Mul R] [Ring R] since Mul is implied by Ring.

Damiano Testa (Jul 31 2024 at 17:34):

This is the one that you linked to above, I think.

Michael Rothgang (Jul 31 2024 at 17:35):

Indeed, I was using your PR as inspiration for how to work with variables syntactically.

Damiano Testa (Jul 31 2024 at 17:35):

Ah, no, the one above is yet another one! The one above, #14675, is one that complains when variable (a : Nat) is available and you type example (a : Nat) ....

Damiano Testa (Jul 31 2024 at 17:37):

In some sense, that linter tries to maximize the use of the variables in scope. I agree with the sentiment that this is not so great: it is better to be more explicit about assumptions!

Damiano Testa (Jul 31 2024 at 17:37):

I think that the "implied typeclass" linter does produce better code, but that one will step on the new variable mechanism.

Damiano Testa (Jul 31 2024 at 17:38):

So, I withheld from developing and adapting mathlib to it until the variable wave washes out.

Michael Rothgang (Jul 31 2024 at 17:45):

Damiano Testa said:

Is this close to what you wanted?

import Lean

open Lean

def getNamesWithType (stx : Syntax) : Array (Syntax × Bool) :=
  if #[``Lean.Parser.Term.implicitBinder, ``Lean.Parser.Term.explicitBinder].contains stx.getKind then
    stx[1].getArgs.map fun s => (s, stx[2][0] == default)
  else #[]

run_cmd
  let stxs := [ `(bracketedBinder| (a : Nat)),  `(bracketedBinder| (b c)),  `(bracketedBinder| {d e f})]
  for stx in stxs do
    logInfo m!"{getNamesWithType stx.raw}"

Yes, it is - thanks a lot! (I actually want to invert the booleans, but that is easy to do.)

Damiano Testa (Jul 31 2024 at 17:45):

Yes, I noticed it and I have already edited the command above!

Damiano Testa (Jul 31 2024 at 17:46):

Make sure not to invert them twice! (The original was indeed the opposite of what you wanted.)

Damiano Testa (Jul 31 2024 at 17:46):

Also, note that (a := 0) is a valid binder and the code above treats it as "no type provided".

Damiano Testa (Jul 31 2024 at 17:46):

Whether or not this is what you want, I do not know!

Michael Rothgang (Jul 31 2024 at 17:50):

Damiano Testa said:

Whether or not this is what you want, I do not know!

Good point; I am not sure yet either. I'll think about it!

Damiano Testa (Jul 31 2024 at 17:51):

Btw, if you want to see a lot of raw binder action, checkout the fully unpolished #15213.

Kyle Miller (Jul 31 2024 at 19:29):

I don't think you should be parsing binders yourself like this. Take a look at the implementation of variable in docs#Lean.Elab.Command.elabVariable

There's docs#Lean.Elab.Command.getBracketedBinderIds for example to get the ids from each binder. Potentially you could even elaborate the binders using docs#Lean.Elab.Term.elabBinders to get the full list with their types, which includes implicit/explicit/etc.

Michael Rothgang (Jul 31 2024 at 19:48):

Thanks, that looks really helpful. I'll take a closer look!


Last updated: May 02 2025 at 03:31 UTC