lean4-htt/src/Lean/AddDecl.lean
Sebastian Ullrich 663df8f7e8
feat: backward.privateInPublic option (#10807)
This PR introduces the `backward.privateInPublic` option to aid in
porting projects to the module system by temporarily allowing access to
private declarations from the public scope, even across modules. A
warning will be generated by such accesses unless
`backward.privateInPublic.warn` is disabled.
2025-10-16 20:51:45 +00:00

215 lines
9.1 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Meta.Sorry
public import Lean.Util.CollectAxioms
public section
namespace Lean
/-- Adds given declaration to the environment, respecting `debug.skipKernelTC`. -/
def Kernel.Environment.addDecl (env : Environment) (opts : Options) (decl : Declaration)
(cancelTk? : Option IO.CancelToken := none) : Except Exception Environment :=
if debug.skipKernelTC.get opts then
addDeclWithoutChecking env decl
else
addDeclCore env (Core.getMaxHeartbeats opts).toUSize decl cancelTk?
private def Environment.addDeclAux (env : Environment) (opts : Options) (decl : Declaration)
(cancelTk? : Option IO.CancelToken := none) : Except Kernel.Exception Environment :=
env.addDeclCore (Core.getMaxHeartbeats opts).toUSize decl cancelTk? (!debug.skipKernelTC.get opts)
private def isNamespaceName : Name → Bool
| .str .anonymous _ => true
| .str p _ => isNamespaceName p
| _ => false
private def registerNamePrefixes (env : Environment) (name : Name) : Environment :=
match name with
| .str _ s =>
if s.get 0 == '_' then
-- Do not register namespaces that only contain internal declarations.
env
else
go env name
| _ => env
where go env
| .str p _ => if isNamespaceName p then go (env.registerNamespace p) p else env
| _ => env
private builtin_initialize privateConstKindsExt : MapDeclarationExtension ConstantKind ←
-- Use `sync` so we can add entries from anywhere without restrictions
mkMapDeclarationExtension (asyncMode := .sync)
/--
Returns the kind of the declaration as originally declared instead of as exported. This information
is stored by `Lean.addDecl` and may be inaccurate if that function was circumvented. Returns `none`
if the declaration was not found.
-/
def getOriginalConstKind? (env : Environment) (declName : Name) : Option ConstantKind := do
-- Use `local` as for asynchronous decls from the current module, `findAsync?` below will yield
-- the same result but potentially earlier (after `addConstAsync` instead of `addDecl`)
privateConstKindsExt.find? (asyncMode := .local) env declName <|>
(env.setExporting false |>.findAsync? declName).map (·.kind)
/--
Checks whether the declaration was originally declared as a theorem; see also
`Lean.getOriginalConstKind?`. Returns `false` if the declaration was not found.
-/
def wasOriginallyTheorem (env : Environment) (declName : Name) : Bool :=
getOriginalConstKind? env declName |>.map (· matches .thm) |>.getD false
/-- If `warn.sorry` is set to true, then, so long as the message log does not already have any errors,
declarations with `sorryAx` generate the "declaration uses 'sorry'" warning. -/
register_builtin_option warn.sorry : Bool := {
defValue := true
descr := "warn about uses of `sorry` in declarations added to the environment"
}
/--
If the `warn.sorry` option is set to true and there are no errors in the log already,
logs a warning if the declaration uses `sorry`.
-/
def warnIfUsesSorry (decl : Declaration) : CoreM Unit := do
if warn.sorry.get (← getOptions) then
if !(← MonadLog.hasErrors) && decl.hasSorry then
-- Find an actual sorry expression to use for 'sorry'.
-- That way the user can hover over it to see its type and use "go to definition" if it is a labeled sorry.
let findSorry : StateRefT (Array (Bool × MessageData)) MetaM Unit := decl.forEachSorryM fun s => do
let s' ← addMessageContext s
modify fun arr => arr.push (s.isSyntheticSorry, s')
let (_, sorries) ← findSorry |>.run #[] |>.run'
-- Prefer reporting a synthetic sorry.
-- These can appear without logged errors if `decl` is referring to declarations with elaboration errors;
-- that's where a user should direct their focus.
if let some (_, s) := sorries.find? (·.1) <|> sorries[0]? then
logWarning <| .tagged `hasSorry m!"declaration uses '{s}'"
else
-- This case should not happen, but it ensures a warning will get logged no matter what.
logWarning <| .tagged `hasSorry m!"declaration uses 'sorry'"
/--
Adds the given declaration to the environment's private scope, deriving a suitable presentation in
the public scope if under the module system and if the declaration is not private. If `forceExpose`
is true, exposes the declaration body, i.e. preserves the full representation in the public scope,
independently of `Environment.isExporting` and even for theorems.
-/
def addDecl (decl : Declaration) (forceExpose := false) : CoreM Unit := do
-- register namespaces for newly added constants; this used to be done by the kernel itself
-- but that is incompatible with moving it to a separate task
-- NOTE: we do not use `getTopLevelNames` here so that inductive types are registered as
-- namespaces
modifyEnv (decl.getNames.foldl registerNamePrefixes)
-- convert `Declaration` to `ConstantInfo` to use as a preliminary value in the environment until
-- kernel checking has finished; not all cases are supported yet
let mut exportedInfo? := none
let (name, info, kind) ← match decl with
| .thmDecl thm =>
if !forceExpose && (← getEnv).header.isModule then
exportedInfo? := some <| .axiomInfo { thm with isUnsafe := false }
pure (thm.name, .thmInfo thm, .thm)
| .defnDecl defn | .mutualDefnDecl [defn] =>
if !forceExpose && (← getEnv).header.isModule && !(← getEnv).isExporting then
exportedInfo? := some <| .axiomInfo { defn with isUnsafe := defn.safety == .unsafe }
pure (defn.name, .defnInfo defn, .defn)
| .opaqueDecl op =>
if !forceExpose && (← getEnv).header.isModule && !(← getEnv).isExporting then
exportedInfo? := some <| .axiomInfo { op with }
pure (op.name, .opaqueInfo op, .opaque)
| .axiomDecl ax => pure (ax.name, .axiomInfo ax, .axiom)
| _ => return (← doAdd)
if decl.getTopLevelNames.all isPrivateName && !(← ResolveName.backward.privateInPublic.getM) then
exportedInfo? := none
else
-- preserve original constant kind in extension if different from exported one
if exportedInfo?.isSome then
modifyEnv (privateConstKindsExt.insert · name kind)
else
exportedInfo? := some info
-- no environment extension changes to report after kernel checking; ensures we do not
-- accidentally wait for this snapshot when querying extension states
let env ← getEnv
let async ← env.addConstAsync (reportExts := false) name kind
(exportedKind? := exportedInfo?.map (.ofConstantInfo))
-- report preliminary constant info immediately
async.commitConst async.asyncEnv (some info) (exportedInfo? <|> info)
setEnv async.mainEnv
let doAddAndCommit := do
setEnv async.asyncEnv
try
doAdd
finally
async.commitCheckEnv (← getEnv)
if Elab.async.get (← getOptions) then
let cancelTk ← IO.CancelToken.new
let checkAct ← Core.wrapAsyncAsSnapshot (cancelTk? := cancelTk) fun _ => doAddAndCommit
let t ← BaseIO.mapTask checkAct env.checked
-- Do not display reporting range; most uses of `addDecl` are for registering auxiliary decls
-- users should not worry about and other callers can add a separate task with ranges
-- themselves, see `MutualDef`.
Core.logSnapshotTask { stx? := none, reportingRange := .skip, task := t, cancelTk? := cancelTk }
else
try
doAddAndCommit
finally
setEnv async.mainEnv
where
doAdd := do
profileitM Exception "type checking" (← getOptions) do
withTraceNode `Kernel (return m!"{exceptEmoji ·} typechecking declarations {decl.getTopLevelNames}") do
warnIfUsesSorry decl
try
let env ← (← getEnv).addDeclAux (← getOptions) decl (← read).cancelTk?
|> ofExceptKernelException
setEnv env
catch ex =>
-- avoid follow-up errors by (trying to) add broken decl as axiom
addAsAxiom
throw ex
addAsAxiom := do
-- try to add as axiom with given type for def/theorem
match decl with
| .defnDecl d | .thmDecl d =>
let fallbackDecl := .axiomDecl {
name := d.name, levelParams := d.levelParams, type := d.type, isUnsafe := false
}
try
let env ← (← getEnv).addDeclAux (← getOptions) fallbackDecl (← read).cancelTk?
|> ofExceptKernelException
setEnv env
return
catch _ => pure ()
| _ => pure ()
-- otherwise, add as axiom with type `sorry`
for n in decl.getNames do
let fallbackDecl := .axiomDecl {
name := n, levelParams := []
type := mkApp2 (mkConst ``sorryAx [1]) (mkSort 0) (mkConst ``true), isUnsafe := false
}
try
let env ← (← getEnv).addDeclAux (← getOptions) fallbackDecl (← read).cancelTk?
|> ofExceptKernelException
setEnv env
return
catch _ => pure ()
def addAndCompile (decl : Declaration) (logCompileErrors : Bool := true) : CoreM Unit := do
addDecl decl
compileDecl decl (logErrors := logCompileErrors)
end Lean