Skip to content

Commit 4810117

Browse files
committed
Defer cleanup of emptied and synthetic declarations during invalidation
During invalidation, declarations can lose all of their backing definitions before the resolver has a chance to recreate them. Queue those declarations for post-resolution cleanup instead of removing them immediately. The cleanup pass now preserves TODO namespaces that still have members, reaps orphaned singleton classes once their members and attached references are gone, re-queues owners that become empty when a child is removed, and loops resolution until cleanup stops producing more work. Also fix remove_document_data so deleting an attached reference like Foo.new detaches the stale singleton reference even if the attached name was already unresolved, and add incremental regression tests for the affected delete, delete-and-readd, and compact-notation cleanup cases.
1 parent f610861 commit 4810117

3 files changed

Lines changed: 605 additions & 28 deletions

File tree

rust/rubydex/src/model/declaration.rs

Lines changed: 33 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
}
@@ -345,6 +353,27 @@ impl Declaration {
345353
all_declarations!(self, it => it.definition_ids.is_empty())
346354
}
347355

356+
/// Returns true if this declaration has no backing definitions and is eligible
357+
/// for removal. Bootstrap declarations (Object, Module, Class) are never
358+
/// removable. Singleton classes are only removable when they have no
359+
/// definitions AND no members — populated singletons serve as namespace
360+
/// parents for class-level methods.
361+
#[must_use]
362+
pub fn has_no_backing_definitions(&self, decl_id: &DeclarationId) -> bool {
363+
if *decl_id == *super::graph::OBJECT_ID
364+
|| *decl_id == *super::graph::MODULE_ID
365+
|| *decl_id == *super::graph::CLASS_ID
366+
{
367+
return false;
368+
}
369+
370+
if matches!(self, Declaration::Namespace(Namespace::SingletonClass(_))) {
371+
return self.has_no_definitions() && self.as_namespace().is_some_and(|ns| ns.members().is_empty());
372+
}
373+
374+
self.has_no_definitions()
375+
}
376+
348377
pub fn add_definition(&mut self, definition_id: DefinitionId) {
349378
all_declarations!(self, it => {
350379
debug_assert!(
@@ -578,6 +607,10 @@ impl Namespace {
578607
all_namespaces!(self, it => it.set_singleton_class_id(declaration_id));
579608
}
580609

610+
pub fn clear_singleton_class_id(&mut self, expected: &DeclarationId) {
611+
all_namespaces!(self, it => it.clear_singleton_class_id(expected));
612+
}
613+
581614
#[must_use]
582615
pub fn owner_id(&self) -> &DeclarationId {
583616
all_namespaces!(self, it => &it.owner_id)

0 commit comments

Comments
 (0)