-
Notifications
You must be signed in to change notification settings - Fork 1
Add unresolve functions and declaration_to_names reverse index
#627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -494,14 +494,51 @@ impl Graph { | |
| &self.names | ||
| } | ||
|
|
||
| /// Converts a `Resolved` `NameRef` back to `Unresolved`, preserving the original `Name` data. | ||
| /// Returns the `DeclarationId` it was previously resolved to, if any. | ||
| fn unresolve_name(&mut self, name_id: NameId) -> Option<DeclarationId> { | ||
| let name_ref = self.names.get(&name_id)?; | ||
|
|
||
| match name_ref { | ||
| NameRef::Resolved(resolved) => { | ||
| let declaration_id = *resolved.declaration_id(); | ||
| let name = resolved.name().clone(); | ||
| self.names.insert(name_id, NameRef::Unresolved(Box::new(name))); | ||
| Some(declaration_id) | ||
| } | ||
| NameRef::Unresolved(_) => None, | ||
| } | ||
| } | ||
|
|
||
| /// Unresolves a constant reference: removes it from the target declaration's reference set | ||
| /// and unresolves its underlying name. | ||
| fn unresolve_reference(&mut self, reference_id: ReferenceId) -> Option<DeclarationId> { | ||
| let constant_ref = self.constant_references.get(&reference_id)?; | ||
| let name_id = *constant_ref.name_id(); | ||
|
|
||
| if let Some(old_decl_id) = self.unresolve_name(name_id) { | ||
| if let Some(declaration) = self.declarations.get_mut(&old_decl_id) { | ||
| declaration.remove_reference(&reference_id); | ||
| } | ||
| Some(old_decl_id) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
|
|
||
| /// Removes a name from the graph entirely. | ||
| fn remove_name(&mut self, name_id: NameId) { | ||
| self.names.remove(&name_id); | ||
| } | ||
|
|
||
| /// Decrements the ref count for a name and removes it if the count reaches zero. | ||
| /// | ||
| /// This does not recursively untrack `parent_scope` or `nesting` names. | ||
| pub fn untrack_name(&mut self, name_id: NameId) { | ||
| if let Some(name_ref) = self.names.get_mut(&name_id) { | ||
| let string_id = *name_ref.str(); | ||
| if !name_ref.decrement_ref_count() { | ||
| self.names.remove(&name_id); | ||
| self.remove_name(name_id); | ||
| } | ||
| self.untrack_string(string_id); | ||
| } | ||
|
|
@@ -714,12 +751,9 @@ impl Graph { | |
| } | ||
|
|
||
| for ref_id in document.constant_references() { | ||
| self.unresolve_reference(*ref_id); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is okay for now, but be careful with mixing the removal and the invalidation of the data. The fact that a constant reference was removed doesn't necessarily mean that the # file1.rb
class Foo
# NameRef(CONST, parent_scope: None, nesting: Foo)
CONST
end
# Delete
# file2.rb
class Foo
# NameRef(CONST, parent_scope: None, nesting: Foo)
CONST
end
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My plan is for this and #589 to ALWAYS unresolve class Foo
include Bar # Bar defines CONST
CONST # resolves to Bar::CONST via ancestor lookup
end And then we create a follow up PR to make this and other invalidation more efficient. |
||
|
|
||
| if let Some(constant_ref) = self.constant_references.remove(ref_id) { | ||
| if let Some(NameRef::Resolved(resolved)) = self.names.get(constant_ref.name_id()) | ||
| && let Some(declaration) = self.declarations.get_mut(resolved.declaration_id()) | ||
| { | ||
| declaration.remove_reference(ref_id); | ||
| } | ||
| self.untrack_name(*constant_ref.name_id()); | ||
| } | ||
| } | ||
|
|
@@ -1743,4 +1777,45 @@ mod tests { | |
|
|
||
| assert!(context.graph().resolve_alias(&DeclarationId::from("Foo")).is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn deleting_sole_definition_removes_the_name_entirely() { | ||
| let mut context = GraphTest::new(); | ||
|
|
||
| context.index_uri("file:///foo.rb", "module Foo; end\nBar"); | ||
| context.index_uri("file:///bar.rb", "module Bar; end"); | ||
| context.resolve(); | ||
|
|
||
| // Bar declaration should have 1 reference (from foo.rb) | ||
| let bar_decl = context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap(); | ||
| assert_eq!(bar_decl.references().len(), 1); | ||
|
|
||
| // Update foo.rb to remove the Bar reference | ||
| context.index_uri("file:///foo.rb", "module Foo; end"); | ||
| context.resolve(); | ||
|
|
||
| let bar_decl = context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap(); | ||
| assert!( | ||
| bar_decl.references().is_empty(), | ||
| "Reference to Bar should be detached from declaration" | ||
| ); | ||
|
|
||
| // Delete bar.rb — the Bar name should be fully removed | ||
| let bar_name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id(); | ||
| context.index_uri("file:///bar.rb", ""); | ||
| context.resolve(); | ||
|
|
||
| assert!( | ||
| context | ||
| .graph() | ||
| .declarations() | ||
| .get(&DeclarationId::from("Bar")) | ||
| .is_none(), | ||
| "Bar declaration should be removed" | ||
| ); | ||
| assert!( | ||
| context.graph().names().get(&bar_name_id).is_none(), | ||
| "Bar name should be removed from the names map" | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating this method as we'll also update
name_dependentshere in follow up PRs.