Skip to content

Commit 99c8b04

Browse files
committed
Defer cleanup of emptied and synthetic declarations during invalidation
1 parent f610861 commit 99c8b04

3 files changed

Lines changed: 566 additions & 50 deletions

File tree

rust/rubydex/src/model/declaration.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ macro_rules! namespace_declaration {
162162
self.singleton_class_id = Some(declaration_id);
163163
}
164164

165+
/// Clears `singleton_class_id` only if it matches `expected`.
166+
/// Prevents accidentally clearing a pointer to a re-created singleton.
167+
pub fn clear_singleton_class_id(&mut self, expected: &DeclarationId) {
168+
if self.singleton_class_id.as_ref() == Some(expected) {
169+
self.singleton_class_id = None;
170+
}
171+
}
172+
165173
pub fn singleton_class_id(&self) -> Option<&DeclarationId> {
166174
self.singleton_class_id.as_ref()
167175
}
@@ -327,6 +335,14 @@ impl Declaration {
327335
}
328336
}
329337

338+
#[must_use]
339+
pub fn as_singleton_class(&self) -> Option<&Namespace> {
340+
match self {
341+
Declaration::Namespace(ns @ Namespace::SingletonClass(_)) => Some(ns),
342+
_ => None,
343+
}
344+
}
345+
330346
#[must_use]
331347
pub fn as_namespace_mut(&mut self) -> Option<&mut Namespace> {
332348
match self {
@@ -345,6 +361,38 @@ impl Declaration {
345361
all_declarations!(self, it => it.definition_ids.is_empty())
346362
}
347363

364+
/// Returns true if this declaration has no backing definitions and is eligible
365+
/// for removal. Bootstrap declarations (Object, Module, Class) are never
366+
/// removable. Singleton classes delegate to `Namespace::is_empty` — they
367+
/// are only removable when they have no definitions AND no members
368+
/// (populated singletons serve as namespace parents for class-level methods).
369+
#[must_use]
370+
pub fn has_no_backing_definitions(&self, decl_id: &DeclarationId) -> bool {
371+
if *decl_id == *super::graph::OBJECT_ID
372+
|| *decl_id == *super::graph::MODULE_ID
373+
|| *decl_id == *super::graph::CLASS_ID
374+
{
375+
return false;
376+
}
377+
378+
if let Some(singleton_class) = self.as_singleton_class() {
379+
return singleton_class.is_empty();
380+
}
381+
382+
self.has_no_definitions()
383+
}
384+
385+
/// Returns true if this is an empty singleton class eligible for cleanup.
386+
/// Checks structural emptiness via `Namespace::is_empty` (no definitions,
387+
/// no members) plus no constant references (which would cause re-creation
388+
/// cycles if the singleton were removed). The caller must additionally
389+
/// verify there are no live descendants before actually removing.
390+
#[must_use]
391+
pub fn is_removable_singleton(&self) -> bool {
392+
self.as_singleton_class()
393+
.is_some_and(|singleton_class| singleton_class.is_empty() && singleton_class.references().is_empty())
394+
}
395+
348396
pub fn add_definition(&mut self, definition_id: DefinitionId) {
349397
all_declarations!(self, it => {
350398
debug_assert!(
@@ -466,6 +514,12 @@ impl Namespace {
466514
}
467515
}
468516

517+
/// Returns true if this namespace has no definitions and no members.
518+
#[must_use]
519+
pub fn is_empty(&self) -> bool {
520+
all_namespaces!(self, it => it.definition_ids.is_empty() && it.members.is_empty())
521+
}
522+
469523
#[must_use]
470524
pub fn references(&self) -> &IdentityHashSet<ConstantReferenceId> {
471525
all_namespaces!(self, it => &it.references)
@@ -578,6 +632,10 @@ impl Namespace {
578632
all_namespaces!(self, it => it.set_singleton_class_id(declaration_id));
579633
}
580634

635+
pub fn clear_singleton_class_id(&mut self, expected: &DeclarationId) {
636+
all_namespaces!(self, it => it.clear_singleton_class_id(expected));
637+
}
638+
581639
#[must_use]
582640
pub fn owner_id(&self) -> &DeclarationId {
583641
all_namespaces!(self, it => &it.owner_id)

0 commit comments

Comments
 (0)