Skip to content

Commit f31d8c9

Browse files
committed
merge #14316
2 parents 808f99f + 50eb538 commit f31d8c9

37 files changed

Lines changed: 1131 additions & 183 deletions

.github/workflows/build-template.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ jobs:
270270
run: |
271271
ulimit -c unlimited # coredumps
272272
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/$TARGET_STAGE -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
273-
if: matrix.test
273+
# `skip-tests`: label for experimental PRs with known-failing tests, so CI still produces
274+
# the `pr-release` toolchain (e.g. for Mathlib benchmarking)
275+
if: matrix.test && !contains(github.event.pull_request.labels.*.name, 'skip-tests')
274276
# copy failed tests' output into the `<failure>` elements shown by the test summary
275277
- name: Embed Test Output
276278
run: |

src/Lean/Attributes.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,11 @@ structure TagAttribute where
180180
def registerTagAttribute (name : Name) (descr : String)
181181
(validate : Name → AttrM Unit := fun _ => pure ()) (ref : Name := by exact decl_name%)
182182
(applicationTime := AttributeApplicationTime.afterTypeChecking)
183-
(asyncMode : EnvExtension.AsyncMode := .mainOnly) : IO TagAttribute := do
183+
(asyncMode : EnvExtension.AsyncMode := .mainOnly)
184+
(tcResolutionAccess : EnvExtension.TCResolutionAccess := .deny) : IO TagAttribute := do
184185
let ext : PersistentEnvExtension Name Name NameSet ← registerPersistentEnvExtension {
185186
name := ref
187+
tcResolutionAccess := tcResolutionAccess
186188
mkInitial := pure {}
187189
addImportedFn := fun _ _ => pure {}
188190
addEntryFn := fun (s : NameSet) n => s.insert n

src/Lean/AuxRecursor.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ def mkRecOnName (indDeclName : Name) : Name := Name.mkStr indDeclName recOnSuf
2323
def mkBRecOnName (indDeclName : Name) : Name := Name.mkStr indDeclName brecOnSuffix
2424
def mkBelowName (indDeclName : Name) : Name := Name.mkStr indDeclName belowSuffix
2525

26-
builtin_initialize auxRecExt : TagDeclarationExtension ← mkTagDeclarationExtension (asyncMode := .async .mainEnv)
26+
builtin_initialize auxRecExt : TagDeclarationExtension ←
27+
-- aux-recursor status is an immutable per-declaration fact
28+
mkTagDeclarationExtension (asyncMode := .async .mainEnv) (tcResolutionAccess := .exempt)
2729

2830
def markAuxRecursor (env : Environment) (declName : Name) : Environment :=
2931
auxRecExt.tag env declName

src/Lean/Class.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ builtin_initialize classExtension : SimplePersistentEnvExtension ClassEntry Clas
7171
registerSimplePersistentEnvExtension {
7272
addEntryFn := ClassState.addEntry
7373
addImportedFn := fun es => (mkStateFromImportedEntries ClassState.addEntry {} es).switch
74+
-- class facts are immutable per declaration and monotone
75+
tcResolutionAccess := .exempt
7476
}
7577

7678
/-- Return `true` if `n` is the name of type class in the given environment. -/

src/Lean/Data/Options.lean

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,61 @@ public section
1515

1616
namespace Lean
1717

18+
/--
19+
Returns whether the option `name` is observable by type class resolution without affecting its
20+
result: tracing, pretty printing and formatting (of messages and trace nodes, which capture the
21+
ambient options object and are rendered later), profiling, diagnostics, debugging, resource
22+
limits (exceeding them throws, and exceptions are not cached), and the elaboration/kernel
23+
options read by constant realization triggered from the search (whose results are registered in
24+
the environment on first use and thus shared regardless of options).
25+
-/
26+
def isSynthInertOption (name : Name) : Bool :=
27+
Name.isPrefixOf `trace name || Name.isPrefixOf `pp name || Name.isPrefixOf `format name ||
28+
Name.isPrefixOf `profiler name || Name.isPrefixOf `diagnostics name ||
29+
Name.isPrefixOf `debug name || Name.isPrefixOf `Elab name || Name.isPrefixOf `Kernel name ||
30+
Name.isPrefixOf `interpreter name || Name.isPrefixOf `server name ||
31+
Name.isPrefixOf `internal name ||
32+
-- limits: exceeding them throws (`Lean.checkExponent` is reached from `Meta.check` during
33+
-- cached-result application), and exceptions are not cached
34+
name == `maxHeartbeats || name == `maxRecDepth ||
35+
Name.isPrefixOf `exponentiation name ||
36+
-- pseudo-option marking pattern-printing mode, read by the delaborator when rendering
37+
-- messages that captured a restricted options object (`Options.getInPattern`)
38+
name == `_inPattern
39+
40+
/--
41+
Access restriction on an `Options` object, enforced by the by-name accessors (`Options.find?`,
42+
`Options.get?`, `Options.contains` and everything built on them): accessing an option outside
43+
the allowed set panics and behaves as if the option were unset. Restricting is a constant-time
44+
flag update (`Options.restrict`) that keeps the underlying entries, so iteration (e.g. `ForIn`)
45+
is unaffected.
46+
-/
47+
inductive OptionsRestriction where
48+
/-- No restriction. -/
49+
| none
50+
/--
51+
Only inert options (`isSynthInertOption`) may be read by name: type class resolution records
52+
every result-relevant option lookup as a dependency of the cache entry it is computing (see
53+
`Lean.Meta.getRecordedOption`), so reads on the search path must go through the recording
54+
accessors, which bypass this restriction via `Options.findUnrestricted?`. A pure by-name read
55+
under this restriction is an unrecorded access and panics.
56+
-/
57+
| tcResolution
58+
59+
/-- Returns whether accessing the option `name` is allowed under the restriction. -/
60+
def OptionsRestriction.allows : OptionsRestriction → Name → Bool
61+
| .none, _ => true
62+
| .tcResolution, name => isSynthInertOption name
63+
1864
structure Options where
1965
private map : NameMap DataValue
2066
/--
2167
Whether any option with prefix `trace` is set. This does *not* imply that any of such option is
2268
set to `true` but it does capture the most common case that no such option has ever been touched.
2369
-/
2470
hasTrace : Bool
71+
/-- Access restriction enforced by the by-name accessors; see `OptionsRestriction`. -/
72+
restriction : OptionsRestriction := .none
2573

2674
namespace Options
2775

@@ -43,14 +91,27 @@ instance : BEq Options where
4391
instance : EmptyCollection Options where
4492
emptyCollection := .empty
4593

46-
@[inline] def find? (o : Options) (k : Name) : Option DataValue :=
94+
/--
95+
Reads the raw entry for `k`, bypassing the access restriction. Callers are responsible for
96+
recording the access as a dependency where required; see `OptionsRestriction.tcResolution` and
97+
`Lean.Meta.getRecordedOption`.
98+
-/
99+
@[inline] def findUnrestricted? (o : Options) (k : Name) : Option DataValue :=
47100
o.map.find? k
48101

102+
@[inline] def find? (o : Options) (k : Name) : Option DataValue :=
103+
if o.restriction.allows k then
104+
o.map.find? k
105+
else
106+
panic! s!"unrecorded access to option `{k}` under the current options restriction; \
107+
reads on the type class resolution path must use the recording accessors, \
108+
see `Lean.OptionsRestriction`"
109+
49110
@[deprecated find? (since := "2026-01-15")]
50111
def find := find?
51112

52113
@[inline] def get? {α : Type} [KVMap.Value α] (o : Options) (k : Name) : Option α :=
53-
o.map.find? k |>.bind KVMap.Value.ofDataValue?
114+
o.find? k |>.bind KVMap.Value.ofDataValue?
54115

55116
@[inline] def get {α : Type} [KVMap.Value α] (o : Options) (k : Name) (defVal : α) : α :=
56117
o.get? k |>.getD defVal
@@ -59,11 +120,21 @@ def find := find?
59120
o.get k defVal
60121

61122
@[inline] def contains (o : Options) (k : Name) : Bool :=
62-
o.map.contains k
123+
if o.restriction.allows k then
124+
o.map.contains k
125+
else
126+
panic! s!"unrecorded access to option `{k}` under the current options restriction; \
127+
reads on the type class resolution path must use the recording accessors, \
128+
see `Lean.OptionsRestriction`"
129+
130+
/-- Restricts by-name access to the options allowed by `r`; see `OptionsRestriction`. -/
131+
@[inline] def restrict (o : Options) (r : OptionsRestriction) : Options :=
132+
{ o with restriction := r }
63133

64134
@[inline] def insert (o : Options) (k : Name) (v : DataValue) : Options where
65135
map := o.map.insert k v
66136
hasTrace := o.hasTrace || (`trace).isPrefixOf k
137+
restriction := o.restriction
67138

68139
def set {α : Type} [KVMap.Value α] (o : Options) (k : Name) (v : α) : Options :=
69140
o.insert k (KVMap.Value.toDataValue v)
@@ -75,10 +146,12 @@ def erase (o : Options) (k : Name) : Options where
75146
map := o.map.erase k
76147
-- `erase` is expected to be used even more rarely than `set` so O(n) is fine
77148
hasTrace := o.map.keys.any (`trace).isPrefixOf
149+
restriction := o.restriction
78150

79151
def mergeBy (f : Name → DataValue → DataValue → DataValue) (o1 o2 : Options) : Options where
80152
map := o1.map.mergeWith f o2.map
81153
hasTrace := o1.hasTrace || o2.hasTrace
154+
restriction := o1.restriction
82155

83156
end Options
84157

@@ -191,6 +264,14 @@ protected structure Decl (α : Type) where
191264
descr : String := ""
192265
deprecation? : Option OptionDeprecation := none
193266

267+
/--
268+
Reads the option bypassing the access restriction, without recording the access; only for reads
269+
that provably cannot influence a type class resolution cache entry, e.g. limits whose exceedance
270+
throws (exceptions are not cached). See `OptionsRestriction.tcResolution`.
271+
-/
272+
protected def getUnrestricted [KVMap.Value α] (opts : Options) (opt : Lean.Option α) : α :=
273+
((opts.findUnrestricted? opt.name).bind KVMap.Value.ofDataValue?).getD opt.defValue
274+
194275
protected def get? [KVMap.Value α] (opts : Options) (opt : Lean.Option α) : Option α :=
195276
opts.get? opt.name
196277

src/Lean/Elab/BuiltinEvalCommand.lean

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ private def mkFormat (e : Expr) : MetaM Expr := do
155155
try
156156
trace[Elab.eval] "Attempting to derive a `Repr` instance for `{.ofConstName name}`"
157157
liftCommandElabM do applyDerivingHandlers ``Repr #[name]
158-
resetSynthInstanceCache
159158
return ← mkRepr e
160159
catch ex =>
161160
trace[Elab.eval] "Failed to use derived `Repr` instance. Exception: {ex.toMessageData}"

src/Lean/Elab/Command.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,10 +1057,10 @@ and do not affect subsequent commands.
10571057
*Warning:* when using this from `MetaM` monads, the `Meta.Cache` caches are *not* reset.
10581058
While the `modifyEnv` function for `MetaM` clears its caches entirely,
10591059
`liftCommandElabM` has no way to reset these caches.
1060-
The type class resolution cache is reset automatically if the command adds or erases instances,
1061-
and scoped instance activation is accounted for in the cache key, but for other changes affecting
1062-
typeclass resolution (e.g. reducibility attributes of pre-existing declarations) you should use
1063-
`Lean.Meta.resetSynthInstanceCache`.
1060+
The type class resolution cache is unaffected by this: its entries record their dependencies
1061+
and self-invalidate when the command changes them (e.g. by adding instances or changing
1062+
reducibility attributes). Other `Meta.Cache` components (e.g. the `whnf` and `isDefEq` caches)
1063+
can however retain results invalidated by the command's environment changes.
10641064
-/
10651065
def liftCommandElabM (cmd : CommandElabM α) (throwOnError : Bool := true) : CoreM α := do
10661066
-- `observing` ensures that if `cmd` throws an exception we still thread state back to `CoreM`.

src/Lean/Elab/PreDefinition/PartialFixpoint/Eqns.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public structure EqnInfo where
2626
deriving Inhabited
2727

2828
public builtin_initialize eqnInfoExt : MapDeclarationExtension EqnInfo ←
29-
mkMapDeclarationExtension (exportEntriesFn := fun env s =>
29+
-- consulted by the reserved-name predicates for fixpoint induction names, so also on
30+
-- resolution search paths; populated only when the declaration is created (monotone)
31+
mkMapDeclarationExtension (tcResolutionAccess := .exempt) (exportEntriesFn := fun env s =>
3032
let all := s.toArray
3133
-- Do not export for non-exposed defs at exported/server levels
3234
let exported := s.filter (fun n _ => env.hasExposedBody n) |>.toArray

src/Lean/Elab/PreDefinition/Structural/Eqns.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ where
148148
throwError "no progress at goal\n{MessageData.ofGoal mvarId}"
149149

150150
public builtin_initialize eqnInfoExt : MapDeclarationExtension EqnInfo ←
151-
mkMapDeclarationExtension (exportEntriesFn := fun env s =>
151+
mkMapDeclarationExtension (tcResolutionAccess := .exempt) (exportEntriesFn := fun env s =>
152152
let all := s.toArray
153153
-- Do not export for non-exposed defs at exported/server levels
154154
let exported := s.filter (fun n _ => env.hasExposedBody n) |>.toArray

src/Lean/Elab/PreDefinition/WF/Eqns.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public structure EqnInfo where
2424
deriving Inhabited
2525

2626
public builtin_initialize eqnInfoExt : MapDeclarationExtension EqnInfo ←
27-
mkMapDeclarationExtension (exportEntriesFn := fun env s =>
27+
mkMapDeclarationExtension (tcResolutionAccess := .exempt) (exportEntriesFn := fun env s =>
2828
let all := s.toArray
2929
-- Do not export for non-exposed defs at exported/server levels
3030
let exported := s.filter (fun n _ => env.hasExposedBody n) |>.toArray

0 commit comments

Comments
 (0)