fix: enable Verso docstrings in where-blocks (#12122)

This PR adds support for Verso docstrings in `where` clauses.

Closes #12066.
This commit is contained in:
David Thrane Christiansen 2026-01-23 15:02:11 +01:00 committed by GitHub
parent 4526cdda5f
commit 4a3401f69a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 109 additions and 7 deletions

View file

@ -20,10 +20,12 @@ structure LetRecDeclView where
declName : Name
parentName? : Option Name
binderIds : Array Syntax
binders : Syntax -- binder syntax for docstring elaboration
type : Expr
mvar : Expr -- auxiliary metavariable used to lift the 'let rec'
valStx : Syntax
termination : TerminationHints
docString? : Option (TSyntax ``Parser.Command.docComment × Bool) := none
structure LetRecView where
decls : Array LetRecDeclView
@ -32,8 +34,9 @@ structure LetRecView where
/- group ("let " >> nonReservedSymbol "rec ") >> sepBy1 (group (optional «attributes» >> letDecl)) ", " >> "; " >> termParser -/
private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
let mut decls : Array LetRecDeclView := #[]
let isVerso := doc.verso.get (← getOptions)
for attrDeclStx in letRec[1][0].getSepArgs do
let docStr? := attrDeclStx[0].getOptional?.map TSyntax.mk
let docStr? := attrDeclStx[0].getOptional?.map (TSyntax.mk ·, isVerso)
let attrOptStx := attrDeclStx[1]
let attrs ← if attrOptStx.isNone then pure #[] else elabDeclAttrs attrOptStx[0]
let decl := attrDeclStx[2][0]
@ -49,12 +52,14 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
if decls.any fun decl => decl.declName == declName then
withRef declId do
throwError "`{.ofConstName declName}` has already been declared"
let binders := decl[1]
let binderStx := decl[1]
checkNotAlreadyDeclared declName
applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration
addDocString' declName binders docStr?
-- Docstring processing is deferred until the declaration is added to the environment.
-- This is necessary for Verso docstrings to work correctly, as they may reference the
-- declaration being defined.
addDeclarationRangesFromSyntax declName decl declId
let binders := binders.getArgs
let binders := binderStx.getArgs
let typeStx := expandOptType declId decl[2]
let (type, binderIds) ← elabBindersEx binders fun xs => do
let type ← elabType typeStx
@ -70,7 +75,7 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
let termination ← elabTerminationHints ⟨attrDeclStx[3]⟩
decls := decls.push {
ref := declId, attrs, shortDeclName, declName, parentName?,
binderIds, type, mvar, valStx, termination
binderIds, binders := binderStx, type, mvar, valStx, termination, docString? := docStr?
}
else
throwUnsupportedSyntax
@ -127,6 +132,8 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
val := value
mvarId := view.mvar.mvarId!
termination
binders := view.binders
docString? := view.docString?
}
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }

View file

@ -1092,8 +1092,8 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo
ref := c.ref
declName := c.toLift.declName
levelParams := [] -- we set it later
binders := mkNullNode -- No docstrings, so we don't need these
modifiers := { modifiers with attrs := c.toLift.attrs }
binders := c.toLift.binders
modifiers := { modifiers with attrs := c.toLift.attrs, docString? := c.toLift.docString? }
kind, type, value,
termination := c.toLift.termination
}

View file

@ -167,6 +167,11 @@ structure LetRecToLift where
val : Expr
mvarId : MVarId
termination : TerminationHints
/-- The binders syntax for the declaration, used for docstring elaboration. -/
binders : Syntax := .missing
/-- The docstring, if present, and whether it's Verso. Docstring processing is deferred until the
declaration is added to the environment (needed for Verso docstrings to work). -/
docString? : Option (TSyntax ``Lean.Parser.Command.docComment × Bool) := none
deriving Inhabited
/--

View file

@ -0,0 +1,90 @@
import Lean
/-!
# Verso Docstrings on `where`-clause declarations
This test checks that Verso docstrings work correctly on declarations in a `where` clause.
-/
section
open Lean Elab Command
deriving instance Repr for VersoDocString
def printVersoDocstring (x : Name) : CommandElabM Unit := do
let docs? ← findInternalDocString? (← getEnv) x
match docs? with
| none => throwError m!"No docstring for {.ofConstName x}"
| some (.inl md) => throwError m!"Only a Markdown docstring for {.ofConstName x}:\n{md}"
| some (.inr v) => IO.println (repr v)
end
set_option doc.verso true
/-- This docstring works. -/
def foo := bar
where
/-- This docstring should also work. -/
bar := 37
-- Verify the docstrings are present
/--
info: { text := #[Lean.Doc.Block.para #[Lean.Doc.Inline.text "This docstring works. "]], subsections := #[] }
-/
#guard_msgs in
#eval printVersoDocstring ``foo
/--
info: { text := #[Lean.Doc.Block.para #[Lean.Doc.Inline.text "This docstring should also work. "]], subsections := #[] }
-/
#guard_msgs in
#eval printVersoDocstring ``foo.bar
-- Test with multiple where declarations
/-- Outer function. -/
def outer := inner1 + inner2
where
/-- First inner function. -/
inner1 := 10
/-- Second inner function is {lean}`outer.inner2`. -/
inner2 := 20
/--
info: { text := #[Lean.Doc.Block.para #[Lean.Doc.Inline.text "First inner function. "]], subsections := #[] }
-/
#guard_msgs in
#eval printVersoDocstring ``outer.inner1
/--
info: { text := #[Lean.Doc.Block.para
#[Lean.Doc.Inline.text "Second inner function is ",
Lean.Doc.Inline.other
{ name := `Lean.Doc.Data.LeanTerm val := Dynamic.mk `Lean.Doc.Data.LeanTerm _ }
#[Lean.Doc.Inline.code "outer.inner2"],
Lean.Doc.Inline.text ". "]],
subsections := #[] }
-/
#guard_msgs in
#eval printVersoDocstring ``outer.inner2
/-- Function with type annotation. -/
def withType := helper
where
/-- What is the type of {name (full := withType.helper)}`helper`?. -/
helper : Nat := 42
/--
info: { text := #[Lean.Doc.Block.para
#[Lean.Doc.Inline.text "What is the type of ",
Lean.Doc.Inline.other
{ name := `Lean.Doc.Data.Const val := Dynamic.mk `Lean.Doc.Data.Const _ }
#[Lean.Doc.Inline.code "helper"],
Lean.Doc.Inline.text "?. "]],
subsections := #[] }
-/
#guard_msgs in
#eval printVersoDocstring ``withType.helper