Zulip Chat Archive
Stream: lean4
Topic: Ranges for Go-to-definition
Damiano Testa (Sep 04 2025 at 20:11):
In the code below, if I Ctrl-Click on in I get redirected to the elaborator for the command in.
Where is the information about the ranges for such redirection? How can I access these ranges?
Thanks!
import Lean
open Nat in
/-!-/
Sebastian Ullrich (Sep 04 2025 at 20:45):
It's just the range of the entire in syntax node, at least where it is not covered by ranges of subcommands. There is no explicit range for the token in itself being used.
Damiano Testa (Sep 04 2025 at 20:47):
Yes, I imagined that this is what it was, but where can I access those ranges?
Sebastian Ullrich (Sep 04 2025 at 20:47):
Syntax.getRange?
Damiano Testa (Sep 04 2025 at 20:47):
I suspect that, when Lean parses the file, it stores somewhere this information and I would like to know where and how can I retrieve it.
Damiano Testa (Sep 04 2025 at 20:48):
Ok, so then were can I retrieve the syntax for the file?
Sebastian Ullrich (Sep 04 2025 at 20:50):
I assume you know how to get the syntax of the current command? Your example does not make clear whether you need more. Only the server currently knows the syntax tree of the entire file.
Damiano Testa (Sep 04 2025 at 20:51):
What I have is a list of declaration names. Looking at the declarationRange extension, I can retrieve the ranges for the corresponding declarations. However, I would like to get the ranges for the commands that created those declarations.
Damiano Testa (Sep 04 2025 at 20:51):
Is there some way to obtain that?
Damiano Testa (Sep 04 2025 at 20:52):
In the example above, I can retrieve the ranges for the module doc, but I would really like to have the ranges for in.
Sebastian Ullrich (Sep 04 2025 at 21:02):
That's not persisted anywhere, no
Damiano Testa (Sep 04 2025 at 21:03):
Ok, so what is the mechanism that Go to definition uses?
Sebastian Ullrich (Sep 04 2025 at 21:09):
As I said, the server has that information. Other components don't.
Damiano Testa (Sep 04 2025 at 21:11):
I do not actually know what the server is, but I gather that it is not something that lean can access?
Damiano Testa (Sep 04 2025 at 21:12):
And so the process is that lean parses the file, temporarily creates the syntax of every command, passes (some of) the information to the server and then discards it?
Robin Arnez (Sep 05 2025 at 07:53):
You can kinda redo what the server does though:
import Lean
open Lean Parser
def fullReparse : CoreM (TSyntax `Lean.Parser.Module.header × Array Command) := do
let fileMap ← getFileMap
let fileName ← getFileName
let ictx : InputContext := {
input := fileMap.source
fileName
fileMap
}
let (header, state, messages) ← parseHeader ictx
let pmctx : ParserModuleContext := {
env := ← getEnv
options := ← getOptions
currNamespace := ← getCurrNamespace
openDecls := ← getOpenDecls
}
let mut state := state
let mut messages := messages
let mut cmds := #[]
repeat
if fileMap.source.atEnd state.pos then
break
let (stx, newState, newMessages) := parseCommand ictx pmctx state messages
state := newState
messages := newMessages
cmds := cmds.push ⟨stx⟩
return (header, cmds)
def whereAmI : CoreM (Option Command) := do
let (_header, cmds) ← fullReparse
let ref ← getRef
let some range := ref.getRange? | return none
for c in cmds do
let some cmdRange := c.raw.getRange? | continue
if cmdRange.includes range then
return some c
return none
elab "whereami" : tactic => do
let some cmd ← whereAmI | throwError "no position found"
Lean.logInfo cmd
example : True := by
whereami
although this might not be fully accurate if there are syntax commands in between
Sebastian Ullrich (Sep 05 2025 at 07:56):
Rather it will just fail on any file with notations, so please don't
Damiano Testa (Sep 05 2025 at 07:57):
For my use-case, it is simpler to add a linter that simply emits the ranges of every command that it inspects.
Damiano Testa (Sep 05 2025 at 07:57):
I was simply hoping to be able to avoid having to rebuild each file, since at some point, the information that I want is computed.
Damiano Testa (Sep 05 2025 at 08:02):
To give some context: I am trying to write automation to remove "old" deprecated declarations. For this, I can look in the environment to find
- which declarations have been deprecated in any given range of time (according to the
sincevalue); - find the ranges of the declarations in their files;
- remove these ranges from the files.
This process fails if there is something like
set_option linter.deprecated false in
@[deprecated (since := "2025-02-30")] alias N := Nat
because the set_option line would be left dangling (and would apply to the following command, if there is one).
Damiano Testa (Sep 05 2025 at 08:03):
If, instead, I can retrieve the command ranges that have generated the declarations, then I am done!
Damiano Testa (Sep 05 2025 at 08:04):
Currently, it seems that the simplest approach is to have a re-parsing step where a linter logs all the command ranges, and then use this information to remove the emitted command ranges that entirely contain the declaration ranges that I want to remove.
Robin Arnez (Sep 05 2025 at 09:24):
Sebastian Ullrich schrieb:
Rather it will just fail on any file with notations, so please don't
I believe it will still work as long as you take the environment from the end of the file but there are definitely some edge cases
Damiano Testa (Sep 05 2025 at 09:26):
Would the code not complain about existing declarations, if you use the end environment?
Robin Arnez (Sep 05 2025 at 09:27):
I mean it's just parsing; the parser doesn't care
Damiano Testa (Sep 05 2025 at 09:28):
Ah, ok, I thought that you were re-building all the file.
Damiano Testa (Sep 05 2025 at 09:29):
Anyway, maybe I can use the linter emitting ranges to emit more information as well and re-purpose it for more than just figuring out if there is an in before a declaration.
Damiano Testa (Sep 05 2025 at 09:30):
At least if feels less wasteful like this! :slight_smile:
Last updated: Dec 20 2025 at 21:32 UTC