diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5acc0aee..41eb4498 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -535,6 +535,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -596,6 +605,29 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -675,6 +707,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.11.1" @@ -739,6 +780,7 @@ dependencies = [ "glob", "libc", "line-index", + "parking_lot", "predicates", "regex", "ruby-prism", @@ -784,6 +826,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -1019,6 +1067,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -1059,7 +1113,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", diff --git a/rust/rubydex/Cargo.toml b/rust/rubydex/Cargo.toml index 2f45df86..aff2e725 100644 --- a/rust/rubydex/Cargo.toml +++ b/rust/rubydex/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["rlib"] test_utils = ["dep:tempfile"] [dependencies] +parking_lot = "0.12" ruby-prism = "1.6.0" url = "2.5.4" xxhash-rust = { version = "0.8.15", features = ["xxh3"] } diff --git a/rust/rubydex/src/model/declaration.rs b/rust/rubydex/src/model/declaration.rs index 2ab5f69a..7c29a6a7 100644 --- a/rust/rubydex/src/model/declaration.rs +++ b/rust/rubydex/src/model/declaration.rs @@ -1,4 +1,4 @@ -use std::sync::{Mutex, MutexGuard, RwLock}; +use parking_lot::{Mutex, MutexGuard, RwLock, RwLockReadGuard}; use crate::model::{ identity_maps::{IdentityHashMap, IdentityHashSet}, @@ -53,156 +53,202 @@ impl<'a> IntoIterator for &'a Ancestors { } } -macro_rules! all_declarations { - ($value:expr, $var:ident => $expr:expr) => { - match $value { - Declaration::Class($var) => $expr, - Declaration::SingletonClass($var) => $expr, - Declaration::Module($var) => $expr, - Declaration::Constant($var) => $expr, - Declaration::Method($var) => $expr, - Declaration::GlobalVariable($var) => $expr, - Declaration::InstanceVariable($var) => $expr, - Declaration::ClassVariable($var) => $expr, - } - }; +/// Base struct for simple declarations like variables and methods +#[derive(Debug)] +pub struct SimpleDeclaration { + /// The fully qualified name of this declaration + name: String, + /// The list of definition IDs that compose this declaration + definition_ids: Vec, + /// The list of references that are made to this declaration + references: Vec, + /// The ID of the owner of this declaration + owner_id: DeclarationId, } -/// Macro to generate a new struct for namespace-like declarations such as classes and modules -macro_rules! namespace_declaration { - ($variant:ident, $name:ident) => { - #[derive(Debug)] - pub struct $name { - /// The fully qualified name of this declaration - name: String, - /// The list of definition IDs that compose this declaration - definition_ids: Vec, - /// The set of references that are made to this declaration - references: IdentityHashSet, - /// The ID of the owner of this declaration. For singleton classes, this is the ID of the attached object - owner_id: DeclarationId, - /// The entities that are owned by this declaration. For example, constants and methods that are defined inside of - /// the namespace. Note that this is a hashmap of unqualified name IDs to declaration IDs. That assists the - /// traversal of the graph when trying to resolve constant references or trying to discover which methods exist in a - /// class - members: IdentityHashMap, - /// The linearized ancestor chain for this declaration. These are the other declarations that this - /// declaration inherits from - ancestors: RwLock, - /// The set of declarations that inherit from this declaration - descendants: Mutex>, - /// The singleton class associated with this declaration - singleton_class_id: Option, +impl SimpleDeclaration { + #[must_use] + pub fn new(name: String, owner_id: DeclarationId) -> Self { + Self { + name, + definition_ids: Vec::new(), + references: Vec::new(), + owner_id, } + } - impl $name { - #[must_use] - pub fn new(name: String, owner_id: DeclarationId) -> Self { - Self { - name, - definition_ids: Vec::new(), - members: IdentityHashMap::default(), - references: IdentityHashSet::default(), - owner_id, - ancestors: RwLock::new(Ancestors::Partial(Vec::new())), - descendants: Mutex::new(IdentityHashSet::default()), - singleton_class_id: None, - } - } - - pub fn extend(&mut self, other: Declaration) { - match other { - Declaration::$variant(it) => { - self.members.extend(it.members); - } - _ => panic!("Tried to merge incompatible declaration types"), - } - } + #[must_use] + pub fn name(&self) -> &str { + &self.name + } - pub fn set_singleton_class_id(&mut self, declaration_id: DeclarationId) { - self.singleton_class_id = Some(declaration_id); - } + #[must_use] + pub fn references(&self) -> &[ReferenceId] { + &self.references + } - pub fn singleton_class_id(&self) -> Option<&DeclarationId> { - self.singleton_class_id.as_ref() - } + #[must_use] + pub fn definitions(&self) -> &[DefinitionId] { + &self.definition_ids + } - #[must_use] - pub fn members(&self) -> &IdentityHashMap { - &self.members - } + #[must_use] + pub fn has_no_definitions(&self) -> bool { + self.definition_ids.is_empty() + } - pub fn add_member(&mut self, string_id: StringId, declaration_id: DeclarationId) { - self.members.insert(string_id, declaration_id); - } + /// Adds a definition ID to this declaration. + /// + /// # Panics + /// + /// Panics in debug builds if `definition_id` is already present. + pub fn add_definition(&mut self, definition_id: DefinitionId) { + debug_assert!( + !self.definition_ids.contains(&definition_id), + "Cannot add the same exact definition to a declaration twice. Duplicate definition IDs" + ); + self.definition_ids.push(definition_id); + } - pub fn remove_member(&mut self, string_id: &StringId) -> Option { - self.members.remove(string_id) - } + pub fn add_reference(&mut self, id: ReferenceId) { + self.references.push(id); + } - #[must_use] - pub fn get_member(&self, string_id: &StringId) -> Option<&DeclarationId> { - self.members.get(string_id) - } + pub fn remove_reference(&mut self, reference_id: &ReferenceId) { + if let Some(pos) = self.references.iter().position(|id| id == reference_id) { + self.references.swap_remove(pos); + self.references.shrink_to_fit(); + } + } - pub fn set_ancestors(&self, ancestors: Ancestors) { - let mut ancestors_lock = self.ancestors.write().unwrap(); - *ancestors_lock = ancestors; - } + pub fn remove_definition(&mut self, definition_id: &DefinitionId) -> bool { + if let Some(pos) = self.definition_ids.iter().position(|id| id == definition_id) { + self.definition_ids.swap_remove(pos); + self.definition_ids.shrink_to_fit(); + true + } else { + false + } + } - #[must_use] - pub fn clone_ancestors(&self) -> Ancestors { - self.ancestors.read().unwrap().clone() - } + #[must_use] + pub fn owner_id(&self) -> &DeclarationId { + &self.owner_id + } - #[must_use] - pub fn has_complete_ancestors(&self) -> bool { - matches!( - *self.ancestors.read().unwrap(), - Ancestors::Complete(_) | Ancestors::Cyclic(_) - ) - } + #[must_use] + pub fn unqualified_name(&self) -> String { + self.name.rsplit("::").next().unwrap_or(&self.name).to_string() + } - pub fn add_descendant(&self, descendant_id: DeclarationId) { - self.descendants.lock().unwrap().insert(descendant_id); - } + pub fn extend(&mut self, other: &SimpleDeclaration) { + self.definition_ids.extend(other.definition_ids.iter().copied()); + self.references.extend(other.references.iter().copied()); + } +} - pub fn descendants(&self) -> MutexGuard<'_, IdentityHashSet> { - self.descendants.lock().unwrap() - } - } - }; +/// Struct for namespace-like declarations such as classes and modules +#[derive(Debug)] +pub struct NamespaceDeclaration { + /// The base simple declaration fields + simple: SimpleDeclaration, + /// The entities that are owned by this declaration. For example, constants and methods that are defined inside of + /// the namespace. Note that this is a hashmap of unqualified name IDs to declaration IDs. That assists the + /// traversal of the graph when trying to resolve constant references or trying to discover which methods exist in a + /// class + members: IdentityHashMap, + /// The linearized ancestor chain for this declaration. These are the other declarations that this + /// declaration inherits from + ancestors: RwLock, + /// The set of declarations that inherit from this declaration + descendants: Mutex>, + /// The singleton class associated with this declaration + singleton_class_id: Option, } -/// Macro to generate a new struct for simple declarations like variables and methods -macro_rules! simple_declaration { - ($name:ident) => { - #[derive(Debug)] - pub struct $name { - /// The fully qualified name of this declaration - name: String, - /// The list of definition IDs that compose this declaration - definition_ids: Vec, - /// The set of references that are made to this declaration - references: IdentityHashSet, - /// The ID of the owner of this declaration - owner_id: DeclarationId, +impl NamespaceDeclaration { + #[must_use] + pub fn new(name: String, owner_id: DeclarationId) -> Self { + Self { + simple: SimpleDeclaration::new(name, owner_id), + members: IdentityHashMap::default(), + ancestors: RwLock::new(Ancestors::Partial(Vec::new())), + descendants: Mutex::new(IdentityHashSet::default()), + singleton_class_id: None, } + } - impl $name { - #[must_use] - pub fn new(name: String, owner_id: DeclarationId) -> Self { - Self { - name, - definition_ids: Vec::new(), - references: IdentityHashSet::default(), - owner_id, - } - } + #[must_use] + pub fn simple(&self) -> &SimpleDeclaration { + &self.simple + } - pub fn extend(&mut self, _other: Declaration) {} - } - }; + pub fn simple_mut(&mut self) -> &mut SimpleDeclaration { + &mut self.simple + } + + pub fn set_singleton_class_id(&mut self, declaration_id: DeclarationId) { + self.singleton_class_id = Some(declaration_id); + } + + pub fn singleton_class_id(&self) -> Option<&DeclarationId> { + self.singleton_class_id.as_ref() + } + + #[must_use] + pub fn members(&self) -> &IdentityHashMap { + &self.members + } + + pub fn add_member(&mut self, string_id: StringId, declaration_id: DeclarationId) { + self.members.insert(string_id, declaration_id); + } + + pub fn remove_member(&mut self, string_id: &StringId) -> Option { + self.members.remove(string_id) + } + + #[must_use] + pub fn get_member(&self, string_id: &StringId) -> Option<&DeclarationId> { + self.members.get(string_id) + } + + /// Sets the ancestor chain for this declaration. + pub fn set_ancestors(&self, ancestors: Ancestors) { + *self.ancestors.write() = ancestors; + } + + /// Returns a read guard to the ancestor chain. + pub fn ancestors(&self) -> RwLockReadGuard<'_, Ancestors> { + self.ancestors.read() + } + + /// Returns a clone of the ancestor chain. + #[must_use] + pub fn clone_ancestors(&self) -> Ancestors { + self.ancestors.read().clone() + } + + /// Returns whether the ancestor chain is fully linearized. + #[must_use] + pub fn has_complete_ancestors(&self) -> bool { + matches!(*self.ancestors.read(), Ancestors::Complete(_) | Ancestors::Cyclic(_)) + } + + /// Adds a descendant to this declaration's set of descendants. + pub fn add_descendant(&self, descendant_id: DeclarationId) { + self.descendants.lock().insert(descendant_id); + } + + /// Returns a mutex guard to the set of descendants. + pub fn descendants(&self) -> MutexGuard<'_, IdentityHashSet> { + self.descendants.lock() + } + + pub fn extend(&mut self, other: &NamespaceDeclaration) { + self.simple.extend(&other.simple); + self.members.extend(other.members.iter().map(|(k, v)| (*k, *v))); + } } /// A `Declaration` represents the global concept of an entity in Ruby. For example, the class `Foo` may be defined 3 @@ -210,105 +256,136 @@ macro_rules! simple_declaration { /// the same fully qualified name #[derive(Debug)] pub enum Declaration { - Class(Box), - SingletonClass(Box), - Module(Box), - Constant(Box), - Method(Box), - GlobalVariable(Box), - InstanceVariable(Box), - ClassVariable(Box), + Class(Box), + SingletonClass(Box), + Module(Box), + Constant(Box), + Method(Box), + GlobalVariable(Box), + InstanceVariable(Box), + ClassVariable(Box), } impl Declaration { - // Extend this declaration with more definitions by moving `other.definition_ids` inside + /// Returns a reference to the underlying [`SimpleDeclaration`] for any declaration type + #[must_use] + pub fn as_simple(&self) -> &SimpleDeclaration { + match self { + Declaration::Class(it) | Declaration::SingletonClass(it) | Declaration::Module(it) => it.simple(), + Declaration::Constant(it) + | Declaration::Method(it) + | Declaration::GlobalVariable(it) + | Declaration::InstanceVariable(it) + | Declaration::ClassVariable(it) => it, + } + } + + /// Returns a mutable reference to the underlying [`SimpleDeclaration`] for any declaration type + pub fn as_simple_mut(&mut self) -> &mut SimpleDeclaration { + match self { + Declaration::Class(it) | Declaration::SingletonClass(it) | Declaration::Module(it) => it.simple_mut(), + Declaration::Constant(it) + | Declaration::Method(it) + | Declaration::GlobalVariable(it) + | Declaration::InstanceVariable(it) + | Declaration::ClassVariable(it) => it, + } + } + + /// Returns a reference to the underlying [`NamespaceDeclaration`] for namespace types (`Class`, `Module`, `SingletonClass`) + /// Returns `None` for simple declaration types + #[must_use] + pub fn as_namespace(&self) -> Option<&NamespaceDeclaration> { + match self { + Declaration::Class(it) | Declaration::SingletonClass(it) | Declaration::Module(it) => Some(it), + Declaration::Constant(_) + | Declaration::Method(_) + | Declaration::GlobalVariable(_) + | Declaration::InstanceVariable(_) + | Declaration::ClassVariable(_) => None, + } + } + + /// Returns a mutable reference to the underlying [`NamespaceDeclaration`] for namespace types + /// Returns `None` for simple declaration types + pub fn as_namespace_mut(&mut self) -> Option<&mut NamespaceDeclaration> { + match self { + Declaration::Class(it) | Declaration::SingletonClass(it) | Declaration::Module(it) => Some(it), + Declaration::Constant(_) + | Declaration::Method(_) + | Declaration::GlobalVariable(_) + | Declaration::InstanceVariable(_) + | Declaration::ClassVariable(_) => None, + } + } + + /// Extend this declaration with more definitions by merging `other` into self + /// + /// # Panics + /// + /// Panics if `other` is a different variant than `self` pub fn extend(&mut self, other: Declaration) { - all_declarations!(self, it => { - it.definition_ids.extend(other.definitions()); - it.references.extend(other.references()); - it.extend(other); - }); + match (self, other) { + (Declaration::Class(a), Declaration::Class(b)) + | (Declaration::SingletonClass(a), Declaration::SingletonClass(b)) + | (Declaration::Module(a), Declaration::Module(b)) => a.extend(&b), + (Declaration::Constant(a), Declaration::Constant(b)) + | (Declaration::Method(a), Declaration::Method(b)) + | (Declaration::GlobalVariable(a), Declaration::GlobalVariable(b)) + | (Declaration::InstanceVariable(a), Declaration::InstanceVariable(b)) + | (Declaration::ClassVariable(a), Declaration::ClassVariable(b)) => a.extend(&b), + _ => panic!("Tried to merge incompatible declaration types"), + } } #[must_use] pub fn name(&self) -> &str { - all_declarations!(self, it => &it.name) + self.as_simple().name() } #[must_use] - pub fn references(&self) -> &IdentityHashSet { - all_declarations!(self, it => &it.references) + pub fn references(&self) -> &[ReferenceId] { + self.as_simple().references() } #[must_use] pub fn definitions(&self) -> &[DefinitionId] { - all_declarations!(self, it => &it.definition_ids) + self.as_simple().definitions() } #[must_use] pub fn has_no_definitions(&self) -> bool { - all_declarations!(self, it => it.definition_ids.is_empty()) + self.as_simple().has_no_definitions() } pub fn add_definition(&mut self, definition_id: DefinitionId) { - all_declarations!(self, it => { - debug_assert!( - !it.definition_ids.contains(&definition_id), - "Cannot add the same exact definition to a declaration twice. Duplicate definition IDs" - ); - - it.definition_ids.push(definition_id); - }); + self.as_simple_mut().add_definition(definition_id); } pub fn add_reference(&mut self, id: ReferenceId) { - all_declarations!(self, it => { - it.references.insert(id); - }); + self.as_simple_mut().add_reference(id); } pub fn remove_reference(&mut self, reference_id: &ReferenceId) { - all_declarations!(self, it => { - it.references.remove(reference_id); - }); + self.as_simple_mut().remove_reference(reference_id); } // Deletes a definition from this declaration pub fn remove_definition(&mut self, definition_id: &DefinitionId) -> bool { - all_declarations!(self, it => { - if let Some(pos) = it.definition_ids.iter().position(|id| id == definition_id) { - it.definition_ids.swap_remove(pos); - it.definition_ids.shrink_to_fit(); - true - } else { - false - } - }) + self.as_simple_mut().remove_definition(definition_id) } #[must_use] pub fn owner_id(&self) -> &DeclarationId { - all_declarations!(self, it => &it.owner_id) + self.as_simple().owner_id() } - // This will change once we fix fully qualified names to not use `::` as separators for everything. Also, we may - // want to actually store this in the struct. Currently, it is only used to cleanup a member that got deleted from - // the graph, so we're avoiding the extra memory cost by computing it on demand. #[must_use] pub fn unqualified_name(&self) -> String { - all_declarations!(self, it => it.name.rsplit("::").next().unwrap_or(&it.name).to_string()) + self.as_simple().unqualified_name() } } -namespace_declaration!(Class, ClassDeclaration); -namespace_declaration!(Module, ModuleDeclaration); -namespace_declaration!(SingletonClass, SingletonClassDeclaration); -simple_declaration!(ConstantDeclaration); -simple_declaration!(MethodDeclaration); -simple_declaration!(GlobalVariableDeclaration); -simple_declaration!(InstanceVariableDeclaration); -simple_declaration!(ClassVariableDeclaration); - #[cfg(test)] mod tests { use super::*; @@ -316,7 +393,7 @@ mod tests { #[test] #[should_panic(expected = "Cannot add the same exact definition to a declaration twice. Duplicate definition IDs")] fn inserting_duplicate_definitions() { - let mut decl = Declaration::Class(Box::new(ClassDeclaration::new( + let mut decl = Declaration::Class(Box::new(NamespaceDeclaration::new( "MyDecl".to_string(), DeclarationId::from("Object"), ))); @@ -329,42 +406,68 @@ mod tests { #[test] fn adding_and_removing_members() { - let decl = Declaration::Class(Box::new(ClassDeclaration::new( + let mut decl = Declaration::Class(Box::new(NamespaceDeclaration::new( "Foo".to_string(), DeclarationId::from("Object"), ))); let member_name_id = StringId::from("Bar"); let member_decl_id = DeclarationId::from("Foo::Bar"); - let Declaration::Class(mut class) = decl else { - panic!("Expected a class declaration"); - }; - class.add_member(member_name_id, member_decl_id); - assert_eq!(class.members.len(), 1); + let ns = decl.as_namespace_mut().unwrap(); + ns.add_member(member_name_id, member_decl_id); + assert_eq!(ns.members().len(), 1); - let removed = class.remove_member(&member_name_id); + let removed = ns.remove_member(&member_name_id); assert_eq!(removed, Some(member_decl_id)); - assert_eq!(class.members.len(), 0); + assert_eq!(ns.members().len(), 0); } #[test] fn unqualified_name() { - let decl = Declaration::Class(Box::new(ClassDeclaration::new( + let decl = Declaration::Class(Box::new(NamespaceDeclaration::new( "Foo".to_string(), DeclarationId::from("Foo"), ))); assert_eq!(decl.unqualified_name(), "Foo"); - let decl = Declaration::Class(Box::new(ClassDeclaration::new( + let decl = Declaration::Class(Box::new(NamespaceDeclaration::new( "Foo::Bar".to_string(), DeclarationId::from("Foo"), ))); assert_eq!(decl.unqualified_name(), "Bar"); - let decl = Declaration::Class(Box::new(ClassDeclaration::new( + let decl = Declaration::Class(Box::new(NamespaceDeclaration::new( "Foo::Bar::baz".to_string(), DeclarationId::from("Foo::Bar"), ))); assert_eq!(decl.unqualified_name(), "baz"); } + + #[test] + fn as_simple_returns_simple_declaration() { + let decl = Declaration::Method(Box::new(SimpleDeclaration::new( + "foo".to_string(), + DeclarationId::from("Object"), + ))); + let simple = decl.as_simple(); + assert_eq!(simple.name(), "foo"); + } + + #[test] + fn as_namespace_returns_namespace_for_classes() { + let decl = Declaration::Class(Box::new(NamespaceDeclaration::new( + "Foo".to_string(), + DeclarationId::from("Object"), + ))); + assert!(decl.as_namespace().is_some()); + } + + #[test] + fn as_namespace_returns_none_for_methods() { + let decl = Declaration::Method(Box::new(SimpleDeclaration::new( + "foo".to_string(), + DeclarationId::from("Object"), + ))); + assert!(decl.as_namespace().is_none()); + } } diff --git a/rust/rubydex/src/model/graph.rs b/rust/rubydex/src/model/graph.rs index 230ec164..3fdba845 100644 --- a/rust/rubydex/src/model/graph.rs +++ b/rust/rubydex/src/model/graph.rs @@ -213,14 +213,11 @@ impl Graph { let mut write_lock = self.declarations.write().unwrap(); if let Some(declaration) = write_lock.get_mut(owner_id) { - match declaration { - Declaration::Class(it) => it.add_member(member_str_id, member_declaration_id), - Declaration::Module(it) => it.add_member(member_str_id, member_declaration_id), - Declaration::SingletonClass(it) => it.add_member(member_str_id, member_declaration_id), - Declaration::Constant(_) => { - // TODO: temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new` - } - _ => panic!("Tried to add member to a declaration that isn't a namespace"), + if let Some(namespace) = declaration.as_namespace_mut() { + namespace.add_member(member_str_id, member_declaration_id); + } else if !matches!(declaration, Declaration::Constant(_)) { + // TODO: temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new` + panic!("Tried to add member to a declaration that isn't a namespace"); } } } @@ -331,19 +328,10 @@ impl Graph { // Clean up any members that pointed to declarations that were removed for (owner_id, member_str_id) in members_to_delete { // Remove the `if` and use `unwrap` once we are indexing RBS files to have `Object` - if let Some(owner) = write_lock.get_mut(&owner_id) { - match owner { - Declaration::Class(owner) => { - owner.remove_member(&member_str_id); - } - Declaration::SingletonClass(owner) => { - owner.remove_member(&member_str_id); - } - Declaration::Module(owner) => { - owner.remove_member(&member_str_id); - } - _ => {} // Nothing happens - } + if let Some(owner) = write_lock.get_mut(&owner_id) + && let Some(namespace) = owner.as_namespace_mut() + { + namespace.remove_member(&member_str_id); } } } diff --git a/rust/rubydex/src/resolution.rs b/rust/rubydex/src/resolution.rs index 1f1c9c6c..e5bccca3 100644 --- a/rust/rubydex/src/resolution.rs +++ b/rust/rubydex/src/resolution.rs @@ -4,11 +4,7 @@ use std::{ }; use crate::model::{ - declaration::{ - Ancestor, Ancestors, ClassDeclaration, ClassVariableDeclaration, ConstantDeclaration, Declaration, - GlobalVariableDeclaration, InstanceVariableDeclaration, MethodDeclaration, ModuleDeclaration, - SingletonClassDeclaration, - }, + declaration::{Ancestor, Ancestors, Declaration, NamespaceDeclaration, SimpleDeclaration}, definitions::{Definition, Mixin}, graph::{CLASS_ID, Graph, MODULE_ID, OBJECT_ID}, identity_maps::{IdentityHashMap, IdentityHashSet}, @@ -83,15 +79,15 @@ pub fn resolve_all(graph: &mut Graph) { let mut write_lock = graph.declarations().write().unwrap(); write_lock.insert( *OBJECT_ID, - Declaration::Class(Box::new(ClassDeclaration::new("Object".to_string(), *OBJECT_ID))), + Declaration::Class(Box::new(NamespaceDeclaration::new("Object".to_string(), *OBJECT_ID))), ); write_lock.insert( *MODULE_ID, - Declaration::Class(Box::new(ClassDeclaration::new("Module".to_string(), *OBJECT_ID))), + Declaration::Class(Box::new(NamespaceDeclaration::new("Module".to_string(), *OBJECT_ID))), ); write_lock.insert( *CLASS_ID, - Declaration::Class(Box::new(ClassDeclaration::new("Class".to_string(), *OBJECT_ID))), + Declaration::Class(Box::new(NamespaceDeclaration::new("Class".to_string(), *OBJECT_ID))), ); } @@ -142,22 +138,22 @@ fn handle_definition_unit( let outcome = match graph.definitions().get(&id).unwrap() { Definition::Class(class) => { handle_constant_declaration(graph, *class.name_id(), id, false, |name, owner_id| { - Declaration::Class(Box::new(ClassDeclaration::new(name, owner_id))) + Declaration::Class(Box::new(NamespaceDeclaration::new(name, owner_id))) }) } Definition::Module(module) => { handle_constant_declaration(graph, *module.name_id(), id, false, |name, owner_id| { - Declaration::Module(Box::new(ModuleDeclaration::new(name, owner_id))) + Declaration::Module(Box::new(NamespaceDeclaration::new(name, owner_id))) }) } Definition::Constant(constant) => { handle_constant_declaration(graph, *constant.name_id(), id, false, |name, owner_id| { - Declaration::Constant(Box::new(ConstantDeclaration::new(name, owner_id))) + Declaration::Constant(Box::new(SimpleDeclaration::new(name, owner_id))) }) } Definition::SingletonClass(singleton) => { handle_constant_declaration(graph, *singleton.name_id(), id, true, |name, owner_id| { - Declaration::SingletonClass(Box::new(SingletonClassDeclaration::new(name, owner_id))) + Declaration::SingletonClass(Box::new(NamespaceDeclaration::new(name, owner_id))) }) } _ => panic!("Expected constant definitions"), @@ -267,34 +263,34 @@ fn handle_remaining_definitions( }; create_declaration(graph, str_id, id, owner_id, |name| { - Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id))) + Declaration::Method(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::AttrAccessor(attr) => { let owner_id = resolve_lexical_owner(graph, *attr.lexical_nesting_id()); create_declaration(graph, *attr.str_id(), id, owner_id, |name| { - Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id))) + Declaration::Method(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::AttrReader(attr) => { let owner_id = resolve_lexical_owner(graph, *attr.lexical_nesting_id()); create_declaration(graph, *attr.str_id(), id, owner_id, |name| { - Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id))) + Declaration::Method(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::AttrWriter(attr) => { let owner_id = resolve_lexical_owner(graph, *attr.lexical_nesting_id()); create_declaration(graph, *attr.str_id(), id, owner_id, |name| { - Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id))) + Declaration::Method(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::GlobalVariable(var) => { let owner_id = *OBJECT_ID; create_declaration(graph, *var.str_id(), id, owner_id, |name| { - Declaration::GlobalVariable(Box::new(GlobalVariableDeclaration::new(name, owner_id))) + Declaration::GlobalVariable(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::InstanceVariable(var) => { @@ -332,9 +328,7 @@ fn handle_remaining_definitions( ); } create_declaration(graph, str_id, id, owner_id, |name| { - Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new( - name, owner_id, - ))) + Declaration::InstanceVariable(Box::new(SimpleDeclaration::new(name, owner_id))) }); continue; } @@ -351,20 +345,14 @@ fn handle_remaining_definitions( drop(declarations); // Method in singleton class - owner is the singleton class itself create_declaration(graph, str_id, id, method_owner_id, |name| { - Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new( - name, - method_owner_id, - ))) + Declaration::InstanceVariable(Box::new(SimpleDeclaration::new(name, method_owner_id))) }); } else { drop(declarations); // Regular instance method // Create an instance variable declaration for the method's owner create_declaration(graph, str_id, id, method_owner_id, |name| { - Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new( - name, - method_owner_id, - ))) + Declaration::InstanceVariable(Box::new(SimpleDeclaration::new(name, method_owner_id))) }); } } @@ -385,7 +373,7 @@ fn handle_remaining_definitions( ); } create_declaration(graph, str_id, id, owner_id, |name| { - Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(name, owner_id))) + Declaration::InstanceVariable(Box::new(SimpleDeclaration::new(name, owner_id))) }); } // If in a singleton class body directly, the owner is the singleton class's singleton class @@ -405,7 +393,7 @@ fn handle_remaining_definitions( ); } create_declaration(graph, str_id, id, owner_id, |name| { - Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(name, owner_id))) + Declaration::InstanceVariable(Box::new(SimpleDeclaration::new(name, owner_id))) }); } _ => { @@ -417,7 +405,7 @@ fn handle_remaining_definitions( // TODO: add diagnostic on the else branch. Defining class variables at the top level crashes if let Some(owner_id) = resolve_class_variable_owner(graph, *var.lexical_nesting_id()) { create_declaration(graph, *var.str_id(), id, owner_id, |name| { - Declaration::ClassVariable(Box::new(ClassVariableDeclaration::new(name, owner_id))) + Declaration::ClassVariable(Box::new(SimpleDeclaration::new(name, owner_id))) }); } } @@ -428,12 +416,12 @@ fn handle_remaining_definitions( let owner_id = resolve_lexical_owner(graph, *alias.lexical_nesting_id()); create_declaration(graph, *alias.new_name_str_id(), id, owner_id, |name| { - Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id))) + Declaration::Method(Box::new(SimpleDeclaration::new(name, owner_id))) }); } Definition::GlobalVariableAlias(alias) => { create_declaration(graph, *alias.new_name_str_id(), id, *OBJECT_ID, |name| { - Declaration::GlobalVariable(Box::new(GlobalVariableDeclaration::new(name, *OBJECT_ID))) + Declaration::GlobalVariable(Box::new(SimpleDeclaration::new(name, *OBJECT_ID))) }); } } @@ -538,7 +526,7 @@ fn get_or_create_singleton_class(graph: &Graph, attached_id: DeclarationId) -> D write_lock .entry(decl_id) - .or_insert_with(|| Declaration::SingletonClass(Box::new(SingletonClassDeclaration::new(name, attached_id)))); + .or_insert_with(|| Declaration::SingletonClass(Box::new(NamespaceDeclaration::new(name, attached_id)))); decl_id } @@ -1084,12 +1072,9 @@ fn search_top_level(graph: &Graph, str_id: StringId) -> Outcome { } fn members_of(decl: &Declaration) -> &IdentityHashMap { - match decl { - Declaration::Class(it) => it.members(), - Declaration::SingletonClass(it) => it.members(), - Declaration::Module(it) => it.members(), - _ => panic!("Tried to get members for a declaration that isn't a namespace"), - } + decl.as_namespace() + .expect("Tried to get members for a declaration that isn't a namespace") + .members() } /// Returns a complexity score for a given name, which is used to sort names for resolution. The complexity is based @@ -1259,48 +1244,38 @@ fn linearize_parent_class(graph: &Graph, definitions: &[&Definition], context: & } fn add_descendant(declaration: &Declaration, descendant_id: DeclarationId) { - match declaration { - Declaration::Class(class) => class.add_descendant(descendant_id), - Declaration::Module(module) => module.add_descendant(descendant_id), - Declaration::SingletonClass(singleton) => singleton.add_descendant(descendant_id), - _ => panic!("Tried to add descendant for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to add descendant for a declaration that isn't a namespace") + .add_descendant(descendant_id); } fn clone_ancestors(declaration: &Declaration) -> Ancestors { - match declaration { - Declaration::Class(class) => class.clone_ancestors(), - Declaration::Module(module) => module.clone_ancestors(), - Declaration::SingletonClass(singleton) => singleton.clone_ancestors(), - _ => panic!("Tried to get ancestors for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to get ancestors for a declaration that isn't a namespace") + .clone_ancestors() } fn has_complete_ancestors(declaration: &Declaration) -> bool { - match declaration { - Declaration::Class(class) => class.has_complete_ancestors(), - Declaration::Module(module) => module.has_complete_ancestors(), - Declaration::SingletonClass(singleton) => singleton.has_complete_ancestors(), - _ => panic!("Tried to check complete ancestors for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to check complete ancestors for a declaration that isn't a namespace") + .has_complete_ancestors() } fn set_ancestors(declaration: &Declaration, ancestors: Ancestors) { - match declaration { - Declaration::Class(class) => class.set_ancestors(ancestors), - Declaration::Module(module) => module.set_ancestors(ancestors), - Declaration::SingletonClass(singleton) => singleton.set_ancestors(ancestors), - _ => panic!("Tried to set ancestors for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to set ancestors for a declaration that isn't a namespace") + .set_ancestors(ancestors); } fn get_member(declaration: &Declaration, str_id: StringId) -> Option<&DeclarationId> { - match declaration { - Declaration::Class(class) => class.get_member(&str_id), - Declaration::Module(module) => module.get_member(&str_id), - Declaration::SingletonClass(singleton) => singleton.get_member(&str_id), - _ => panic!("Tried to get member for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to get member for a declaration that isn't a namespace") + .get_member(&str_id) } fn mixins_of(definition: &Definition) -> Option<&[Mixin]> { @@ -1313,21 +1288,17 @@ fn mixins_of(definition: &Definition) -> Option<&[Mixin]> { } fn singleton_class_id(declaration: &Declaration) -> Option<&DeclarationId> { - match declaration { - Declaration::Class(class) => class.singleton_class_id(), - Declaration::Module(module) => module.singleton_class_id(), - Declaration::SingletonClass(singleton) => singleton.singleton_class_id(), - _ => panic!("Tried to get singleton class ID for a declaration that isn't a namespace"), - } + declaration + .as_namespace() + .expect("Tried to get singleton class ID for a declaration that isn't a namespace") + .singleton_class_id() } fn set_singleton_class_id(declaration: &mut Declaration, id: DeclarationId) { - match declaration { - Declaration::Class(class) => class.set_singleton_class_id(id), - Declaration::Module(module) => module.set_singleton_class_id(id), - Declaration::SingletonClass(singleton) => singleton.set_singleton_class_id(id), - _ => panic!("Tried to set singleton class ID for a declaration that isn't a namespace"), - } + declaration + .as_namespace_mut() + .expect("Tried to set singleton class ID for a declaration that isn't a namespace") + .set_singleton_class_id(id); } #[cfg(test)]