feat: stricter meta check for temporary programs in native_decide etc (#13005)

This PR further enforces that all modules used in compile-time execution
must be meta imported in preparation for enabling
https://github.com/leanprover/lean4/pull/10291

# Breaking changes

Metaprograms that call `compileDecl` directly may now need to call
`markMeta` first where appropriate, possibly based on the value of
`isMarkedMeta` of existing decls. `addAndCompile` should be split into
`addDecl` and `compileDecl` for this in order to insert the call in
between.
This commit is contained in:
Sebastian Ullrich 2026-03-20 16:51:18 +01:00 committed by GitHub
parent 609a05a90a
commit 6f98a76d01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 66 additions and 43 deletions

View file

@ -17,31 +17,11 @@ private builtin_initialize metaExt : TagDeclarationExtension ←
-- set by `addPreDefinitions`; if we ever make `def` elaboration async, it should be moved to
-- remain on the main environment branch
mkTagDeclarationExtension (asyncMode := .async .mainEnv)
/--
Environment extension collecting declarations *could* have been marked as `meta` by the user but
were not, so should not allow access to `meta` declarations to surface phase distinction errors as
soon as possible.
-/
private builtin_initialize notMetaExt : EnvExtension NameSet ←
registerEnvExtension
(mkInitial := pure {})
(asyncMode := .async .mainEnv)
(replay? := some fun _ _ newEntries s => newEntries.foldl (·.insert) s)
/-- Marks in the environment extension that the given declaration has been declared by the user as `meta`. -/
def markMeta (env : Environment) (declName : Name) : Environment :=
metaExt.tag env declName
/--
Marks the given declaration as not being annotated with `meta` even if it could have been by the
user.
-/
def markNotMeta (env : Environment) (declName : Name) : Environment :=
if declName.isAnonymous then -- avoid panic from `modifyState` on partial input
env
else
notMetaExt.modifyState (asyncDecl := declName) env (·.insert declName)
/-- Returns true iff the user has declared the given declaration as `meta`. -/
def isMarkedMeta (env : Environment) (declName : Name) : Bool :=
metaExt.isTagged env declName
@ -102,13 +82,12 @@ def getIRPhases (env : Environment) (declName : Name) : IRPhases := Id.run do
else
env.header.modules[idx]?.map (·.irPhases) |>.get!
| none =>
if isMarkedMeta env declName then
.comptime
else if notMetaExt.getState env |>.contains declName then
.runtime
else
-- Allow `meta`->non-`meta` references in the same module for auxiliary declarations the user
-- could not have marked as `meta` themselves.
if env.find? declName |>.all (·.isCtor) then
-- Do not check ctors (trivial) or decls not in env (compiler-generated)
.all
else if isMarkedMeta env declName then
.comptime
else
.runtime
end Lean

View file

@ -11,6 +11,7 @@ public import Lean.Data.Lsp.Basic
public import Lean.Expr
public import Init.Data.String.Search
public import Init.Data.Array.GetLit
meta import Lean.Data.Json.FromToJson.Basic
public section

View file

@ -559,10 +559,14 @@ def elabUnsafe : TermElab := fun stx expectedType? =>
let t ← elabTermAndSynthesize t expectedType?
if (← logUnassignedUsingErrorInfos (← getMVars t)) then
throwAbortTerm
let t ← mkAuxDefinitionFor (← mkAuxName `unsafe) t
let t ← mkAuxDefinitionFor (compile := false) (← mkAuxName `unsafe) t
let .const unsafeFn unsafeLvls .. := t.getAppFn | unreachable!
let .defnInfo unsafeDefn ← getConstInfo unsafeFn | unreachable!
let implName ← mkAuxName `unsafe_impl
if (← read).declName?.any (isMarkedMeta (← getEnv)) then
modifyEnv (markMeta · unsafeFn)
modifyEnv (markMeta · implName)
compileDecls #[unsafeFn]
addDecl <| Declaration.opaqueDecl {
name := implName
type := unsafeDefn.type

View file

@ -388,6 +388,8 @@ private opaque evalFilePath (stx : Syntax) : TermElabM System.FilePath
if compile then
-- Inline as changing visibility should not affect run time.
setInlineAttribute name
if (← read).declName?.any (isMarkedMeta (← getEnv)) then
modifyEnv (markMeta · name)
compileDecls #[name]
return e
else

View file

@ -76,7 +76,7 @@ inductive RecKind where
/-- Codegen-relevant modifiers. -/
inductive ComputeKind where
| regular | «meta» | «noncomputable»
deriving Inhabited, BEq
deriving Inhabited, BEq, Repr
/-- Flags and data added to declarations (eg docstrings, attributes, `private`, `unsafe`, `partial`, ...). -/
structure Modifiers where

View file

@ -7,6 +7,7 @@ module
prelude
public import Lean.Widget.UserWidget
meta import Lean.Widget.UserWidget
public section

View file

@ -1073,6 +1073,8 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo
return if (← inferType c.toLift.type).isProp then .theorem else .def
else
pure kind
if modifiers.isMeta then
modifyEnv (markMeta · c.toLift.declName)
return preDefs.push {
ref := c.ref
declName := c.toLift.declName

View file

@ -217,9 +217,7 @@ private def addNonRecAux (docCtx : LocalContext × LocalInstances) (preDef : Pre
-- Tags may have been added by `elabMutualDef` already, but that is not the only caller
| .meta => if !isMarkedMeta (← getEnv) preDef.declName then modifyEnv (markMeta · preDef.declName)
| .noncomputable => if !isNoncomputable (asyncMode := .local) (← getEnv) preDef.declName then modifyEnv (addNoncomputable · preDef.declName)
| _ =>
if !preDef.kind.isTheorem then
modifyEnv (markNotMeta · preDef.declName)
| _ => pure ()
if compile && shouldGenCodeFor preDef then
compileDecl decl
if applyAttrAfterCompilation then

View file

@ -289,7 +289,7 @@ builtin_initialize registerBuiltinAttribute {
/-- Elaborate `register_try?_tactic` command -/
@[builtin_command_elab registerTryTactic]
meta def elabRegisterTryTactic : Command.CommandElab := fun stx => do
def elabRegisterTryTactic : Command.CommandElab := fun stx => do
if `Lean.Elab.Tactic.Try ∉ (← getEnv).header.moduleNames then
logWarning "Add `import Lean.Elab.Tactic.Try` before using the `register_try?_tactic` command."
return

View file

@ -10,6 +10,7 @@ prelude
public import Lean.Message
public import Lean.EnvExtension
public import Lean.DocString.Links
meta import Lean.Message
public section

View file

@ -91,19 +91,24 @@ public def mkCtorIdx (indName : Name) : MetaM Unit :=
modifyEnv fun env => addProtected env declName
if info.numCtors = 1 then
setInlineAttribute declName .macroInline
if isMarkedMeta (← getEnv) indName then
modifyEnv (markMeta · declName)
compileDecl decl
enableRealizationsForConst declName
-- Deprecated alias for enumeration types (which used to have `toCtorIdx`)
if (← isEnumType indName) then
let aliasName := mkToCtorIdxName indName
addAndCompile (.defnDecl (← mkDefinitionValInferringUnsafe
addDecl (.defnDecl (← mkDefinitionValInferringUnsafe
(name := aliasName)
(levelParams := info.levelParams)
(type := declType)
(value := mkConst declName us)
(hints := ReducibilityHints.abbrev)
))
if isMarkedMeta (← getEnv) indName then
modifyEnv (markMeta · aliasName)
compileDecls #[aliasName]
modifyEnv fun env => addToCompletionBlackList env aliasName
modifyEnv fun env => addProtected env aliasName
setReducibleAttribute aliasName

View file

@ -23,7 +23,8 @@ unsafe def evalExprCore (α) (value : Expr) (checkType : Expr → MetaM Unit)
if value.getUsedConstants.all (← getEnv).isImportedConst then
modifyEnv fun env => env.importEnv?.getD env
let name ← mkFreshUserName `_tmp
-- Private name to ensure we do not check for deps being imported publicly
let name := mkPrivateName (← getEnv) (← mkFreshUserName `_tmp)
let value ← instantiateMVars value
let us := collectLevelParams {} value |>.params
if value.hasMVar then
@ -35,12 +36,14 @@ unsafe def evalExprCore (α) (value : Expr) (checkType : Expr → MetaM Unit)
value, hints := ReducibilityHints.opaque,
safety
}
modifyEnv (markMeta · name)
-- compilation will invariably wait on `checked`
let _ ← traceBlock "compiler env" (← getEnv).checked
-- now that we've already waited, async would just introduce (minor) overhead and trigger
-- `Task.get` blocking debug code
withOptions (Elab.async.set · false) do
withOptions (Compiler.compiler.postponeCompile.set · false) do
withOptions (Compiler.compiler.relaxedMetaCheck.set · true) do
addAndCompile decl
evalConst (checkMeta := checkMeta) α name

View file

@ -51,11 +51,13 @@ public def nativeEqTrue (tacticName : Name) (e : Expr) (axiomDeclRange? : Option
hints := .abbrev
safety := .safe
}
modifyEnv (markMeta · auxDeclName)
try
-- disable async/separate codegen so we can catch its exceptions; we don't want to report
-- `evalConst` failures below when the actual reason was a codegen failure
withOptions (Elab.async.set · false) do
withOptions (Compiler.compiler.postponeCompile.set · false) do
withOptions (Compiler.compiler.relaxedMetaCheck.set · true) do
addAndCompile decl
catch ex =>
throwError m!"Tactic `{tacticName}` failed. Error: {ex.toMessageData}"

View file

@ -6,7 +6,7 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Data.Repr
import Init.MetaTypes -- shake: keep (dependency of `simp +decide`, fix)
meta import Init.MetaTypes -- shake: keep (dependency of `simp +decide`, fix)
public section
namespace Lean.Meta.Grind
/--

View file

@ -9,6 +9,7 @@ prelude
import Lean.Server.CodeActions
import Lean.Meta.Tactic.ExposeNames
public import Lean.Widget.UserWidget
meta import Lean.Widget.UserWidget
public section

View file

@ -8,6 +8,8 @@ module
prelude
public import Lean.Parser.Do
import Lean.DocString.Parser
meta import Lean.Parser.Do
meta import Lean.DocString.Parser
public section

View file

@ -9,6 +9,7 @@ prelude
public import Lean.Parser.Module.Syntax
meta import Lean.Parser.Module.Syntax
import Init.While
meta import Lean.Parser.Extra
public section

View file

@ -9,6 +9,7 @@ prelude
public import Lean.Parser.Attr
public import Lean.Parser.Level
public import Lean.Parser.Term.Doc
meta import Lean.Parser.Basic
/-!
This module contains the bare minimum of term syntax that's required to get documentation syntax to

View file

@ -12,6 +12,7 @@ public import Lean.Meta.Structure
public import Lean.PrettyPrinter.Formatter
public import Lean.PrettyPrinter.Parenthesizer
meta import Lean.Parser.Command
meta import Lean.PrettyPrinter.Delaborator.DeclWithSig
public section
@ -1547,11 +1548,6 @@ def delabSorry : Delab := whenPPOption getPPNotation <| whenNotPPOption getPPExp
else
withOverApp 2 `(sorry)
open Parser Command Term in
@[run_builtin_parser_attribute_hooks]
-- use `termParser` instead of `declId` so we can reuse `delabConst`
private meta def declSigWithId := leading_parser termParser maxPrec >> declSig
private unsafe def evalSyntaxConstantUnsafe (env : Environment) (opts : Options) (constName : Name) : ExceptT String Id Syntax :=
env.evalConstCheck Syntax opts ``Syntax constName

View file

@ -0,0 +1,18 @@
/-
Copyright (c) 2026 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich, Leonardo de Moura, Gabriel Ebner, Mario Carneiro
-/
module
prelude
public import Lean.Parser.Types
import Lean.Parser.Command
namespace Lean.PrettyPrinter.Delaborator
open Parser Command Term
@[run_builtin_parser_attribute_hooks]
-- use `termParser` instead of `declId` so we can reuse `delabConst`
public def declSigWithId : Parser := leading_parser termParser maxPrec >> declSig

View file

@ -125,7 +125,7 @@ def registerRpcProcedure (method : Name) : CoreM Unit := do
let stx ← ``(wrapRpcProcedure $(quote method) _ _ $(mkIdent method))
let c ← Lean.Elab.Term.elabTerm stx procT
instantiateMVars c
addAndCompile <| Declaration.defnDecl {
let decl := Declaration.defnDecl {
name := wrappedName
type := procT
value := proc
@ -133,6 +133,9 @@ def registerRpcProcedure (method : Name) : CoreM Unit := do
levelParams := []
hints := ReducibilityHints.opaque
}
addDecl decl
modifyEnv (markMeta · wrappedName)
compileDecl decl
setEnv <| userRpcProcedures.insert (← getEnv) method wrappedName
builtin_initialize registerBuiltinAttribute {

View file

@ -8,6 +8,7 @@ module
prelude
public import Lean.Data.Json
import Init.Data.Nat.Fold
meta import Init.Data.Nat.Fold
import Lake.Util.String
public import Init.Data.String.Search
public import Init.Data.String.Extra

View file

@ -1,5 +1,7 @@
module
meta import Init.Data.String
/-!
# Tests for `String` functions
-/

View file

@ -1,5 +1,5 @@
module
import Std.Data.HashSet.Basic
meta import Std.Data.HashSet.Basic
/-!
Tests for `String.Slice` functions