@@ -209,6 +209,8 @@ public def ofNames (names : Array Name) : ConstTrie Unit := Id.run do
209209
210210end ConstTrie
211211
212+ -- `Thunk` fields have no `sizeOf` theorems
213+ set_option genSizeOfSpec false in
212214/--
213215Merged view of the per-module constant prefix trees of all imported modules. Node keys and lookup
214216work like in `ConstTrie`; subtrees whose prefix occurs in only a single module are borrowed
@@ -223,6 +225,15 @@ public inductive ImportedConsts (α : Type) where
223225 -/
224226 | merged (key : Name) (entry? : Option (α × Nat)) (children : Array (ImportedConsts α))
225227 (childIndex : ByteArray)
228+ /--
229+ Like `merged`, but with the children computed on first descent from the candidate subtrees
230+ sharing the prefix, one level at a time. Thunk forcing is thread-safe and supported on
231+ persistent objects, so lazy nodes are safe to share across elaboration threads. Used for views
232+ whose `Duplicate`s nobody consumes; entry lookups at the prefix itself answer directly from
233+ `cands` without forcing.
234+ -/
235+ | lazy (key : Name) (cands : Array (Nat × ConstTrie α))
236+ (expanded : Thunk (Array (ImportedConsts α) × ByteArray))
226237
227238public instance : Inhabited (ImportedConsts α) := ⟨.merged .anonymous none #[] .empty⟩
228239
@@ -233,15 +244,31 @@ public def empty : ImportedConsts α := .merged .anonymous none #[] .empty
233244def key : ImportedConsts α → Name
234245 | .mod _ t => t.key
235246 | .merged k .. => k
247+ | .lazy k .. => k
236248
237249/-- Creates a `merged` node, computing the child index; `children` must be sorted by key hash. -/
238250public def mkMerged (key : Name) (entry? : Option (α × Nat))
239251 (children : Array (ImportedConsts α)) : ImportedConsts α :=
240252 .merged key entry? children (buildChildIndex children ImportedConsts.key)
241253
254+ /-- The entry of the prefix shared by `cands`: the first candidate value, with its module. -/
255+ private def candsEntry? (cands : Array (Nat × ConstTrie α)) : Option (α × Nat) := Id.run do
256+ for (i, t) in cands do
257+ if let some v := t.val? then
258+ return some (v, i)
259+ return none
260+
242261/-- Walks from `t`, the node for `path[i]` (or the root for `i = path.size`), to `path[0]`. -/
243262private partial def findValAux : ImportedConsts α → Array Name → Nat → Option α
244263 | .mod _ tr, path, i => tr.findAux path i
264+ | .lazy _ cands expanded, path, i =>
265+ if i == 0 then
266+ (candsEntry? cands).map (·.1 )
267+ else
268+ let (cs, hs) := expanded.get
269+ match findHashIdx? hs cs key path[i - 1 ]! with
270+ | some j => findValAux cs[j]! path (i - 1 )
271+ | none => none
245272 | .merged _ e? cs hs, path, i =>
246273 if i == 0 then
247274 match e? with
@@ -257,6 +284,14 @@ private partial def findModIdxAux : ImportedConsts α → Array Name → Nat →
257284 match tr.findAux path i with
258285 | some _ => some modIdx
259286 | none => none
287+ | .lazy _ cands expanded, path, i =>
288+ if i == 0 then
289+ (candsEntry? cands).map (·.2 )
290+ else
291+ let (cs, hs) := expanded.get
292+ match findHashIdx? hs cs key path[i - 1 ]! with
293+ | some j => findModIdxAux cs[j]! path (i - 1 )
294+ | none => none
260295 | .merged _ e? cs hs, path, i =>
261296 if i == 0 then
262297 match e? with
@@ -272,6 +307,14 @@ private partial def findEntryAux : ImportedConsts α → Array Name → Nat →
272307 match tr.findAux path i with
273308 | some v => some (v, modIdx)
274309 | none => none
310+ | .lazy _ cands expanded, path, i =>
311+ if i == 0 then
312+ candsEntry? cands
313+ else
314+ let (cs, hs) := expanded.get
315+ match findHashIdx? hs cs key path[i - 1 ]! with
316+ | some j => findEntryAux cs[j]! path (i - 1 )
317+ | none => none
275318 | .merged _ e? cs hs, path, i =>
276319 if i == 0 then
277320 e?
@@ -392,6 +435,55 @@ public def mergeModuleTrees (trees : Array (Nat × ConstTrie α)) :
392435 ImportedConsts α × Array (Duplicate α) :=
393436 mergeCands .anonymous trees |>.run #[]
394437
438+ /-- `mergeCands` without duplicate reporting, producing lazily expanded nodes. -/
439+ private partial def mergeLazy (key : Name) (cands : Array (Nat × ConstTrie α)) :
440+ ImportedConsts α :=
441+ if cands.size == 1 then
442+ let (i, t) := cands[0 ]!
443+ .mod i t
444+ else
445+ .lazy key cands (Thunk.mk fun _ => expandLazy cands)
446+ where
447+ expandLazy (cands : Array (Nat × ConstTrie α)) :
448+ Array (ImportedConsts α) × ByteArray := Id.run do
449+ let mut total := 0
450+ for (_, t) in cands do
451+ total := total + t.children.size
452+ let mut all : Array (UInt64 × Nat × ConstTrie α) := .mkEmpty total
453+ for (i, t) in cands do
454+ for c in t.children do
455+ all := all.push (c.key.hash, i, c)
456+ let sorted := all.qsort fun (h₁, i₁, _) (h₂, i₂, _) => h₁ < h₂ || (h₁ == h₂ && i₁ < i₂)
457+ let mut children := #[]
458+ let mut idx := 0
459+ while idx < sorted.size do
460+ let (hash₀, i, c) := sorted[idx]!
461+ idx := idx + 1
462+ if idx == sorted.size || sorted[idx]!.1 != hash₀ then
463+ -- sole child with this key hash, so from a single module: borrow the subtree
464+ children := children.push (.mod i c)
465+ else
466+ -- gather the run of children with the same key hash, partitioned by actual key in case
467+ -- of hash collisions
468+ let mut groups : Array (Array (Nat × ConstTrie α)) := #[#[(i, c)]]
469+ while idx < sorted.size && sorted[idx]!.1 == hash₀ do
470+ let (_, i, c) := sorted[idx]!
471+ match groups.findIdx? fun g => lastPartEq g[0 ]!.2 .key c.key with
472+ | some gi => groups := groups.modify gi (·.push (i, c))
473+ | none => groups := groups.push #[(i, c)]
474+ idx := idx + 1
475+ for g in groups do
476+ children := children.push (mergeLazy g[0 ]!.2 .key g)
477+ return (children, buildChildIndex children ImportedConsts.key)
478+
479+ /--
480+ Like `mergeModuleTrees`, but merging each node's children on first descent instead of eagerly.
481+ No `Duplicate`s are reported; an entry declared by several modules resolves to the first
482+ module's value, as in the eager merge. Intended for views whose duplicates are not consumed.
483+ -/
484+ public def mergeModuleTreesLazy (trees : Array (Nat × ConstTrie α)) : ImportedConsts α :=
485+ mergeLazy .anonymous trees
486+
395487/--
396488Replaces the node for `n` using `f`. `n` must be a prefix shared by several modules, such as a
397489`Duplicate` name; single-module subtrees are never modified.
@@ -406,6 +498,7 @@ where
406498 @[inline] modifyChild (n : Name) (f : ImportedConsts α → ImportedConsts α) :
407499 ImportedConsts α → ImportedConsts α
408500 | t@(.mod ..) => t
501+ | t@(.lazy ..) => t
409502 | .merged k e? cs hs =>
410503 match findHashIdx? hs cs key n with
411504 | some i => .merged k e? (cs.modify i f) hs
@@ -426,6 +519,11 @@ public partial def foldlM [Monad m] (t : ImportedConsts α) (f : σ → Name →
426519 if let some (v, _) := e? then
427520 s ← f s k v
428521 cs.foldlM (fun s c => c.foldlM f s) s
522+ | .lazy k cands expanded =>
523+ let mut s := init
524+ if let some (v, _) := candsEntry? cands then
525+ s ← f s k v
526+ (expanded.get).1 .foldlM (fun s c => c.foldlM f s) s
429527
430528public def foldl (t : ImportedConsts α) (f : σ → Name → α → σ) (init : σ) : σ :=
431529 t.foldlM (m := Id) f init
@@ -440,6 +538,11 @@ public partial def foldlEntriesM [Monad m] (t : ImportedConsts α)
440538 if let some e := e? then
441539 s ← f s k e
442540 cs.foldlM (fun s c => c.foldlEntriesM f s) s
541+ | .lazy k cands expanded =>
542+ let mut s := init
543+ if let some e := candsEntry? cands then
544+ s ← f s k e
545+ (expanded.get).1 .foldlM (fun s c => c.foldlEntriesM f s) s
443546
444547public def forM [Monad m] (t : ImportedConsts α) (f : Name → α → m PUnit) : m PUnit :=
445548 t.foldlM (fun _ n v => f n v) ⟨⟩
0 commit comments