rubydex 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -3
  3. data/THIRD_PARTY_LICENSES.html +34 -1
  4. data/exe/rdx +131 -55
  5. data/ext/rubydex/declaration.c +1 -1
  6. data/ext/rubydex/definition.c +32 -4
  7. data/ext/rubydex/graph.c +14 -3
  8. data/ext/rubydex/query.c +105 -0
  9. data/ext/rubydex/query.h +8 -0
  10. data/ext/rubydex/reference.c +60 -0
  11. data/ext/rubydex/rubydex.c +2 -0
  12. data/ext/rubydex/utils.c +12 -0
  13. data/ext/rubydex/utils.h +5 -0
  14. data/lib/rubydex/version.rb +1 -1
  15. data/rbi/rubydex.rbi +22 -0
  16. data/rust/Cargo.lock +7 -0
  17. data/rust/rubydex/Cargo.toml +1 -0
  18. data/rust/rubydex/benches/graph_memory.rs +22 -4
  19. data/rust/rubydex/src/compile_assertions.rs +15 -0
  20. data/rust/rubydex/src/diagnostic.rs +1 -1
  21. data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
  22. data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
  23. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
  24. data/rust/rubydex/src/main.rs +2 -124
  25. data/rust/rubydex/src/model/declaration.rs +0 -11
  26. data/rust/rubydex/src/model/definitions.rs +27 -26
  27. data/rust/rubydex/src/model/document.rs +43 -7
  28. data/rust/rubydex/src/model/graph.rs +40 -28
  29. data/rust/rubydex/src/model/id.rs +55 -0
  30. data/rust/rubydex/src/model/ids.rs +21 -9
  31. data/rust/rubydex/src/model/name.rs +35 -7
  32. data/rust/rubydex/src/model/references.rs +16 -13
  33. data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
  34. data/rust/rubydex/src/query/cypher/schema.rs +790 -0
  35. data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
  36. data/rust/rubydex/src/query/cypher/tests.rs +228 -0
  37. data/rust/rubydex/src/query/cypher.rs +57 -0
  38. data/rust/rubydex/src/query.rs +2 -0
  39. data/rust/rubydex/src/resolution.rs +248 -227
  40. data/rust/rubydex/src/resolution_tests.rs +263 -65
  41. data/rust/rubydex-sys/src/declaration_api.rs +6 -3
  42. data/rust/rubydex-sys/src/definition_api.rs +27 -7
  43. data/rust/rubydex-sys/src/graph_api.rs +158 -14
  44. data/rust/rubydex-sys/src/reference_api.rs +58 -12
  45. metadata +8 -2
@@ -2,7 +2,10 @@ use std::fmt::Display;
2
2
 
3
3
  use crate::{
4
4
  assert_mem_size,
5
- model::ids::{DeclarationId, NameId, StringId},
5
+ model::{
6
+ id::id_from_parts,
7
+ ids::{DeclarationId, NameId, StringId},
8
+ },
6
9
  };
7
10
 
8
11
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -127,12 +130,19 @@ impl Name {
127
130
 
128
131
  #[must_use]
129
132
  pub fn id(&self) -> NameId {
130
- NameId::from(&format!(
131
- "{}{}{}",
132
- self.str,
133
- self.parent_scope,
134
- self.nesting.map_or(String::from("None"), |id| id.to_string())
135
- ))
133
+ // We need to include both the variant and the bytes of the parent scope when present
134
+ let (parent_tag, parent_id): (u8, u64) = match self.parent_scope {
135
+ ParentScope::None => (0, 0),
136
+ ParentScope::TopLevel => (1, 0),
137
+ ParentScope::Some(id) => (2, id.get()),
138
+ ParentScope::Attached(id) => (3, id.get()),
139
+ };
140
+ let (nesting_tag, nesting_id): (u8, u64) = match self.nesting {
141
+ None => (0, 0),
142
+ Some(id) => (1, id.get()),
143
+ };
144
+
145
+ id_from_parts!(NameId; self.str.get(), parent_tag, parent_id, nesting_tag, nesting_id)
136
146
  }
137
147
  }
138
148
 
@@ -297,4 +307,22 @@ mod tests {
297
307
  );
298
308
  assert_eq!(name_7.id(), name_8.id());
299
309
  }
310
+
311
+ #[test]
312
+ fn parent_scope_variants_are_distinct() {
313
+ let inner = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
314
+
315
+ let ids = [
316
+ Name::new(StringId::from("Foo"), ParentScope::None, None).id(),
317
+ Name::new(StringId::from("Foo"), ParentScope::TopLevel, None).id(),
318
+ Name::new(StringId::from("Foo"), ParentScope::Some(inner), None).id(),
319
+ Name::new(StringId::from("Foo"), ParentScope::Attached(inner), None).id(),
320
+ ];
321
+
322
+ for (i, a) in ids.iter().enumerate() {
323
+ for b in &ids[i + 1..] {
324
+ assert_ne!(a, b);
325
+ }
326
+ }
327
+ }
300
328
  }
@@ -1,6 +1,9 @@
1
1
  use crate::{
2
2
  assert_mem_size,
3
- model::ids::{ConstantReferenceId, MethodReferenceId, NameId, StringId, UriId},
3
+ model::{
4
+ id::id_from_parts,
5
+ ids::{ConstantReferenceId, MethodReferenceId, NameId, StringId, UriId},
6
+ },
4
7
  offset::Offset,
5
8
  };
6
9
 
@@ -43,13 +46,13 @@ impl ConstantReference {
43
46
 
44
47
  #[must_use]
45
48
  pub fn id(&self) -> ConstantReferenceId {
46
- ConstantReferenceId::from(&format!(
47
- "{}:{}:{}-{}",
48
- self.name_id,
49
- self.uri_id,
49
+ id_from_parts!(
50
+ ConstantReferenceId;
51
+ self.name_id.get(),
52
+ self.uri_id.get(),
50
53
  self.offset.start(),
51
- self.offset.end()
52
- ))
54
+ self.offset.end(),
55
+ )
53
56
  }
54
57
  }
55
58
 
@@ -100,12 +103,12 @@ impl MethodRef {
100
103
 
101
104
  #[must_use]
102
105
  pub fn id(&self) -> MethodReferenceId {
103
- MethodReferenceId::from(&format!(
104
- "{}:{}:{}-{}",
105
- self.str,
106
- self.uri_id,
106
+ id_from_parts!(
107
+ MethodReferenceId;
108
+ self.str.get(),
109
+ self.uri_id.get(),
107
110
  self.offset.start(),
108
- self.offset.end()
109
- ))
111
+ self.offset.end(),
112
+ )
110
113
  }
111
114
  }
@@ -611,20 +611,8 @@ impl<'a> RubyOperationBuilder<'a> {
611
611
  {
612
612
  if let Some(arguments) = node.arguments() {
613
613
  for argument in &arguments.arguments() {
614
- match argument {
615
- ruby_prism::Node::SymbolNode { .. } => {
616
- let symbol = argument.as_symbol_node().unwrap();
617
- if let Some(value_loc) = symbol.value_loc() {
618
- let name = Self::location_to_string(&value_loc);
619
- f(name, value_loc);
620
- }
621
- }
622
- ruby_prism::Node::StringNode { .. } => {
623
- let string = argument.as_string_node().unwrap();
624
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
625
- f(name, argument.location());
626
- }
627
- _ => {}
614
+ if let Some((name, location)) = Self::extract_literal_name(&argument) {
615
+ f(name, location);
628
616
  }
629
617
  }
630
618
  }
@@ -899,28 +887,29 @@ impl<'a> RubyOperationBuilder<'a> {
899
887
 
900
888
  fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
901
889
  let receiver = node.receiver();
890
+ let call_name = String::from_utf8_lossy(node.name().as_slice());
902
891
 
903
- let receiver_name_id = match receiver {
892
+ let receiver_name_id = match &receiver {
904
893
  Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
905
- self.index_constant_reference(&receiver.unwrap(), true)
894
+ self.index_constant_reference(receiver.as_ref().unwrap(), true)
906
895
  }
907
896
  Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
908
897
  Some(Nesting::Method { .. }) => return,
909
898
  None => {
910
899
  self.add_diagnostic(
911
- Rule::InvalidPrivateConstant,
900
+ Rule::InvalidConstantVisibility,
912
901
  Offset::from_prism_location(&node.location()),
913
- "Private constant called at top level".to_string(),
902
+ format!("`{call_name}` called at top level"),
914
903
  );
915
904
  return;
916
905
  }
917
906
  _ => None,
918
907
  },
919
- _ => {
908
+ Some(other) => {
920
909
  self.add_diagnostic(
921
- Rule::InvalidPrivateConstant,
922
- Offset::from_prism_location(&node.location()),
923
- "Dynamic receiver for private constant".to_string(),
910
+ Rule::InvalidConstantVisibility,
911
+ Offset::from_prism_location(&other.location()),
912
+ format!("Dynamic receiver for `{call_name}`"),
924
913
  );
925
914
  return;
926
915
  }
@@ -931,28 +920,13 @@ impl<'a> RubyOperationBuilder<'a> {
931
920
  };
932
921
 
933
922
  for argument in &arguments.arguments() {
934
- let (name, location) = match argument {
935
- ruby_prism::Node::SymbolNode { .. } => {
936
- let symbol = argument.as_symbol_node().unwrap();
937
- if let Some(value_loc) = symbol.value_loc() {
938
- (Self::location_to_string(&value_loc), value_loc)
939
- } else {
940
- continue;
941
- }
942
- }
943
- ruby_prism::Node::StringNode { .. } => {
944
- let string = argument.as_string_node().unwrap();
945
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
946
- (name, argument.location())
947
- }
948
- _ => {
949
- self.add_diagnostic(
950
- Rule::InvalidPrivateConstant,
951
- Offset::from_prism_location(&argument.location()),
952
- "Private constant called with non-symbol argument".to_string(),
953
- );
954
- continue;
955
- }
923
+ let Some((name, location)) = Self::extract_literal_name(&argument) else {
924
+ self.add_diagnostic(
925
+ Rule::InvalidConstantVisibility,
926
+ Offset::from_prism_location(&argument.location()),
927
+ format!("`{call_name}` called with a non-literal argument"),
928
+ );
929
+ continue;
956
930
  };
957
931
 
958
932
  let str_id = self.intern_string(name);
@@ -1067,6 +1041,22 @@ impl<'a> RubyOperationBuilder<'a> {
1067
1041
  Some(())
1068
1042
  }
1069
1043
 
1044
+ fn extract_literal_name<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
1045
+ match arg {
1046
+ ruby_prism::Node::SymbolNode { .. } => {
1047
+ let symbol = arg.as_symbol_node().unwrap();
1048
+ let value_loc = symbol.value_loc()?;
1049
+ Some((Self::location_to_string(&value_loc), value_loc))
1050
+ }
1051
+ ruby_prism::Node::StringNode { .. } => {
1052
+ let string = arg.as_string_node().unwrap();
1053
+ let name = String::from_utf8_lossy(string.unescaped()).to_string();
1054
+ Some((name, arg.location()))
1055
+ }
1056
+ _ => None,
1057
+ }
1058
+ }
1059
+
1070
1060
  fn is_attr_call(arg: &ruby_prism::Node) -> bool {
1071
1061
  arg.as_call_node().is_some_and(|call| {
1072
1062
  let receiver = call.receiver();
@@ -1116,6 +1106,18 @@ impl<'a> RubyOperationBuilder<'a> {
1116
1106
  ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
1117
1107
  ) {
1118
1108
  self.create_method_visibility_operation(&arg, visibility, DefinitionFlags::empty());
1109
+ if visibility == Visibility::ModuleFunction {
1110
+ // `module_function` also creates a public singleton method, so we emit a
1111
+ // second def for the singleton side. The two defs stay separate (rather than
1112
+ // shared) so reverse-lookup invalidation can detach `Foo#bar` and
1113
+ // `Foo::<Foo>#bar` independently. The `SINGLETON_METHOD_VISIBILITY` flag
1114
+ // routes this one to the singleton class.
1115
+ self.create_method_visibility_operation(
1116
+ &arg,
1117
+ visibility,
1118
+ DefinitionFlags::SINGLETON_METHOD_VISIBILITY,
1119
+ );
1120
+ }
1119
1121
  } else {
1120
1122
  let arg_offset = Offset::from_prism_location(&arg.location());
1121
1123
  let message = if Self::is_attr_call(&arg) {
@@ -1135,21 +1137,8 @@ impl<'a> RubyOperationBuilder<'a> {
1135
1137
  visibility: Visibility,
1136
1138
  flags: DefinitionFlags,
1137
1139
  ) {
1138
- let (name, location) = match arg {
1139
- ruby_prism::Node::SymbolNode { .. } => {
1140
- let symbol = arg.as_symbol_node().unwrap();
1141
- if let Some(value_loc) = symbol.value_loc() {
1142
- (Self::location_to_string(&value_loc), value_loc)
1143
- } else {
1144
- return;
1145
- }
1146
- }
1147
- ruby_prism::Node::StringNode { .. } => {
1148
- let string = arg.as_string_node().unwrap();
1149
- let name = String::from_utf8_lossy(string.unescaped()).to_string();
1150
- (name, arg.location())
1151
- }
1152
- _ => return,
1140
+ let Some((name, location)) = Self::extract_literal_name(arg) else {
1141
+ return;
1153
1142
  };
1154
1143
 
1155
1144
  let str_id = self.intern_string(format!("{name}()"));