Zulip Chat Archive
Stream: lean4
Topic: Accessing `Expr` of a def across modules
Snir Broshi (Feb 11 2026 at 19:34):
module
import Lean.Elab.Term
open Lean
run_elab do
let env ← getEnv
let k : SyntaxNodeKind := `Lean.Parser.Tactic.exact
if let some constantInfo := env.find? k then
match constantInfo with
| .axiomInfo _ => logInfo m!"axiomInfo"
| .defnInfo _ => logInfo m!"defnInfo"
| .thmInfo _ => logInfo m!"thmInfo"
| .opaqueInfo _ => logInfo m!"opaqueInfo"
| .quotInfo _ => logInfo m!"quotInfo"
| .inductInfo _ => logInfo m!"inductInfo"
| .ctorInfo _ => logInfo m!"ctorInfo"
| .recInfo _ => logInfo m!"recInfo"
logInfo m!"Has expr: {constantInfo.value?.isSome}"
This prints axiomInfo and Has expr: false, and after removing the module line it prints defnInfo and Has expr: true.
Is this a bug, or an intended feature of the module system? I know the module system can be used to avoid @[expose]ing defs, but I expected opaqueInfo if it was the problem. Why axiomInfo?
The context is that Batteries' #help is currently broken in modules:
module
import Batteries
/-- error: no tactic declarations start with exact -/
#guard_msgs in
#help tactic exact
It gets ConstantInfos from the environment then extracts the first token of their syntax using the Expr.
Is there a way to fix it or is #help in modules fundamentally broken?
Perhaps all the tactics just need to be @[expose]d?
Robin Arnez (Feb 11 2026 at 20:38):
Well, opaqueInfo wouldn't work because OpaqueVal still contains a value and the whole premise of @[no_expose] is that you don't get the value by default in imported modules (to avoid needing the memory for that and to avoid build dependencies). Now, when you have an axiom and want to find out what it originally was, you can use docs#Lean.getOriginalConstKind?. This will obviously not help you at all in fixing #help though, for that you can use docs#Lean.Parser.mkParserOfConstant:
import Mathlib
open Lean Parser
run_meta
let cat := `term
let categories := (parserExtension.getState (← getEnv)).categories
let some category := categories.find? cat | throwError "non-existent category {cat}"
for (kind, _) in category.kinds do
let (leading, parser) ← Lean.Parser.mkParserOfConstant categories kind
IO.println s!"{if leading then "leading" else "trailing"} parser {kind}: {parser.info.firstTokens}"
Last updated: Feb 28 2026 at 14:05 UTC