rubydex 0.2.5 → 0.2.6

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -16
  3. data/THIRD_PARTY_LICENSES.html +6 -6
  4. data/ext/rubydex/definition.c +33 -2
  5. data/ext/rubydex/document.c +36 -0
  6. data/ext/rubydex/graph.c +32 -18
  7. data/ext/rubydex/handle.h +21 -5
  8. data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
  9. data/lib/rubydex/errors.rb +8 -0
  10. data/lib/rubydex/location.rb +24 -0
  11. data/lib/rubydex/version.rb +1 -1
  12. data/lib/rubydex.rb +1 -0
  13. data/rbi/rubydex.rbi +29 -12
  14. data/rust/Cargo.lock +3 -3
  15. data/rust/rubydex/Cargo.toml +7 -1
  16. data/rust/rubydex/src/dot.rs +609 -0
  17. data/rust/rubydex/src/indexing/rbs_indexer.rs +19 -1
  18. data/rust/rubydex/src/indexing/ruby_indexer.rs +4 -0
  19. data/rust/rubydex/src/lib.rs +1 -1
  20. data/rust/rubydex/src/main.rs +8 -5
  21. data/rust/rubydex/src/model/built_in.rs +5 -2
  22. data/rust/rubydex/src/model/comment.rs +2 -0
  23. data/rust/rubydex/src/model/declaration.rs +1 -0
  24. data/rust/rubydex/src/model/definitions.rs +13 -1
  25. data/rust/rubydex/src/model/document.rs +2 -0
  26. data/rust/rubydex/src/model/encoding.rs +2 -0
  27. data/rust/rubydex/src/model/graph.rs +51 -13
  28. data/rust/rubydex/src/model/identity_maps.rs +3 -0
  29. data/rust/rubydex/src/model/keywords.rs +3 -0
  30. data/rust/rubydex/src/model/name.rs +2 -0
  31. data/rust/rubydex/src/model/string_ref.rs +2 -0
  32. data/rust/rubydex/src/model/visibility.rs +3 -0
  33. data/rust/rubydex/src/operation/applier.rs +1 -0
  34. data/rust/rubydex/src/operation/mod.rs +1 -0
  35. data/rust/rubydex/src/operation/ruby_builder.rs +4 -0
  36. data/rust/rubydex/src/query.rs +114 -33
  37. data/rust/rubydex/src/resolution.rs +16 -8
  38. data/rust/rubydex/src/resolution_tests.rs +132 -0
  39. data/rust/rubydex/tests/cli.rs +17 -61
  40. data/rust/rubydex-mcp/Cargo.toml +9 -3
  41. data/rust/rubydex-sys/Cargo.toml +9 -2
  42. data/rust/rubydex-sys/src/definition_api.rs +72 -2
  43. data/rust/rubydex-sys/src/document_api.rs +28 -0
  44. data/rust/rubydex-sys/src/graph_api.rs +1 -3
  45. metadata +4 -4
  46. data/rust/rubydex/src/visualization/dot.rs +0 -192
  47. data/rust/rubydex/src/visualization.rs +0 -6
@@ -1811,6 +1811,7 @@ impl Visit<'_> for RubyIndexer<'_> {
1811
1811
  let name = Self::location_to_string(&node.name_loc());
1812
1812
  let str_id = self.local_graph.intern_string(format!("{name}()"));
1813
1813
  let offset = Offset::from_prism_location(&node.location());
1814
+ let name_offset = Offset::from_prism_location(&node.name_loc());
1814
1815
  let parent_nesting_id = self.current_nesting_definition_id();
1815
1816
  let parameters = self.collect_parameters(node);
1816
1817
  let is_singleton = node.receiver().is_some();
@@ -1862,6 +1863,7 @@ impl Visit<'_> for RubyIndexer<'_> {
1862
1863
  str_id,
1863
1864
  self.uri_id,
1864
1865
  offset.clone(),
1866
+ name_offset.clone(),
1865
1867
  comments.clone(),
1866
1868
  flags.clone(),
1867
1869
  parent_nesting_id,
@@ -1878,6 +1880,7 @@ impl Visit<'_> for RubyIndexer<'_> {
1878
1880
  str_id,
1879
1881
  self.uri_id,
1880
1882
  offset,
1883
+ name_offset,
1881
1884
  comments,
1882
1885
  flags,
1883
1886
  parent_nesting_id,
@@ -1895,6 +1898,7 @@ impl Visit<'_> for RubyIndexer<'_> {
1895
1898
  str_id,
1896
1899
  self.uri_id,
1897
1900
  offset,
1901
+ name_offset,
1898
1902
  comments,
1899
1903
  flags,
1900
1904
  parent_nesting_id,
@@ -1,5 +1,6 @@
1
1
  pub mod compile_assertions;
2
2
  pub mod diagnostic;
3
+ pub mod dot;
3
4
  pub mod errors;
4
5
  pub mod indexing;
5
6
  pub mod integrity;
@@ -12,7 +13,6 @@ pub mod position;
12
13
  pub mod query;
13
14
  pub mod resolution;
14
15
  pub mod stats;
15
- pub mod visualization;
16
16
 
17
17
  #[cfg(any(test, feature = "test_utils"))]
18
18
  pub mod test_utils;
@@ -2,6 +2,7 @@ use clap::{Parser, ValueEnum};
2
2
  use std::{collections::HashSet, mem};
3
3
 
4
4
  use rubydex::{
5
+ dot,
5
6
  indexing::{self, IndexerBackend},
6
7
  integrity, listing,
7
8
  model::graph::Graph,
@@ -10,7 +11,6 @@ use rubydex::{
10
11
  memory::MemoryStats,
11
12
  timer::{Timer, time_it},
12
13
  },
13
- visualization::dot,
14
14
  };
15
15
 
16
16
  #[derive(Parser, Debug)]
@@ -23,8 +23,11 @@ struct Args {
23
23
  #[arg(long = "stop-after", help = "Stop after the given stage")]
24
24
  stop_after: Option<StopAfter>,
25
25
 
26
- #[arg(long = "visualize")]
27
- visualize: bool,
26
+ #[arg(long = "dot", help = "Output a DOT graph visualization")]
27
+ dot: bool,
28
+
29
+ #[arg(long = "show-builtins", help = "Include built-in declarations in DOT output")]
30
+ show_builtins: bool,
28
31
 
29
32
  #[arg(long = "stats", help = "Show detailed performance statistics")]
30
33
  stats: bool,
@@ -171,8 +174,8 @@ fn main() {
171
174
  }
172
175
 
173
176
  // Generate visualization or print statistics
174
- if args.visualize {
175
- println!("{}", dot::generate(&graph));
177
+ if args.dot {
178
+ println!("{}", dot::DotBuilder::generate(&graph, args.show_builtins));
176
179
  } else {
177
180
  println!("Indexed {} files", graph.documents().len());
178
181
  println!("Found {} names", graph.declarations().len());
@@ -7,10 +7,13 @@ use crate::{
7
7
  model::{
8
8
  declaration::{ClassDeclaration, Declaration, Namespace},
9
9
  graph::Graph,
10
- ids::DeclarationId,
10
+ ids::{DeclarationId, UriId},
11
11
  },
12
12
  };
13
13
 
14
+ pub const BUILT_IN_URI: &str = "rubydex:built-in";
15
+ pub static BUILT_IN_URI_ID: LazyLock<UriId> = LazyLock::new(|| UriId::from(BUILT_IN_URI));
16
+
14
17
  pub static KERNEL_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Kernel"));
15
18
  pub static BASIC_OBJECT_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("BasicObject"));
16
19
  pub static OBJECT_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Object"));
@@ -27,7 +30,7 @@ pub fn add_built_in_data(graph: &mut Graph) {
27
30
  // We need definitions to ensure that ancestor linearization happens naturally through the algorithm. Trying to set
28
31
  // ancestors directly on declarations doesn't work because the algorithm erases the ancestors and there are no
29
32
  // definitions to inform it of the superclasses and mixins.
30
- let uri = Url::parse("rubydex:built-in").unwrap();
33
+ let uri = Url::parse(BUILT_IN_URI).unwrap();
31
34
  let source = r"
32
35
  class BasicObject
33
36
  end
@@ -1,3 +1,4 @@
1
+ use crate::assert_mem_size;
1
2
  use crate::offset::Offset;
2
3
 
3
4
  #[derive(Debug, Clone)]
@@ -5,6 +6,7 @@ pub struct Comment {
5
6
  offset: Offset,
6
7
  string: String,
7
8
  }
9
+ assert_mem_size!(Comment, 32);
8
10
 
9
11
  impl Comment {
10
12
  #[must_use]
@@ -16,6 +16,7 @@ pub enum Ancestor {
16
16
  /// A partial ancestor that is missing linearization
17
17
  Partial(NameId),
18
18
  }
19
+ assert_mem_size!(Ancestor, 16);
19
20
 
20
21
  /// The ancestor chain and its current state
21
22
  #[derive(Debug, Clone)]
@@ -43,6 +43,7 @@ bitflags! {
43
43
  const SINGLETON_METHOD_VISIBILITY = 0b0100;
44
44
  }
45
45
  }
46
+ assert_mem_size!(DefinitionFlags, 1);
46
47
 
47
48
  impl DefinitionFlags {
48
49
  #[must_use]
@@ -181,6 +182,7 @@ impl Definition {
181
182
  Definition::Class(d) => Some(d.name_offset()),
182
183
  Definition::Module(d) => Some(d.name_offset()),
183
184
  Definition::SingletonClass(d) => Some(d.name_offset()),
185
+ Definition::Method(d) => Some(d.name_offset()),
184
186
  _ => None,
185
187
  }
186
188
  }
@@ -199,6 +201,7 @@ pub enum Mixin {
199
201
  Prepend(PrependDefinition),
200
202
  Extend(ExtendDefinition),
201
203
  }
204
+ assert_mem_size!(Mixin, 16);
202
205
 
203
206
  impl Mixin {
204
207
  #[must_use]
@@ -891,6 +894,7 @@ pub enum Signatures {
891
894
  /// Used for RBS definitions with more than one overload.
892
895
  Overloaded(Box<[Signature]>),
893
896
  }
897
+ assert_mem_size!(Signatures, 24);
894
898
 
895
899
  impl Signatures {
896
900
  /// Returns all signatures as a slice, regardless of variant.
@@ -915,6 +919,7 @@ pub struct MethodDefinition {
915
919
  str_id: StringId,
916
920
  uri_id: UriId,
917
921
  offset: Offset,
922
+ name_offset: Offset,
918
923
  flags: DefinitionFlags,
919
924
  comments: Box<[Comment]>,
920
925
  lexical_nesting_id: Option<DefinitionId>,
@@ -923,7 +928,7 @@ pub struct MethodDefinition {
923
928
  receiver: Option<Receiver>,
924
929
  }
925
930
 
926
- assert_mem_size!(MethodDefinition, 96);
931
+ assert_mem_size!(MethodDefinition, 104);
927
932
 
928
933
  /// The receiver of a singleton method definition.
929
934
  #[derive(Debug, Clone)]
@@ -943,6 +948,7 @@ impl MethodDefinition {
943
948
  str_id: StringId,
944
949
  uri_id: UriId,
945
950
  offset: Offset,
951
+ name_offset: Offset,
946
952
  comments: Box<[Comment]>,
947
953
  flags: DefinitionFlags,
948
954
  lexical_nesting_id: Option<DefinitionId>,
@@ -954,6 +960,7 @@ impl MethodDefinition {
954
960
  str_id,
955
961
  uri_id,
956
962
  offset,
963
+ name_offset,
957
964
  flags,
958
965
  comments,
959
966
  lexical_nesting_id,
@@ -983,6 +990,11 @@ impl MethodDefinition {
983
990
  &self.offset
984
991
  }
985
992
 
993
+ #[must_use]
994
+ pub fn name_offset(&self) -> &Offset {
995
+ &self.name_offset
996
+ }
997
+
986
998
  #[must_use]
987
999
  pub fn comments(&self) -> &[Comment] {
988
1000
  &self.comments
@@ -3,6 +3,7 @@ use std::path::PathBuf;
3
3
  use line_index::LineIndex;
4
4
  use url::Url;
5
5
 
6
+ use crate::assert_mem_size;
6
7
  use crate::diagnostic::Diagnostic;
7
8
  use crate::model::ids::{ConstantReferenceId, DefinitionId, MethodReferenceId};
8
9
 
@@ -17,6 +18,7 @@ pub struct Document {
17
18
  constant_reference_ids: Vec<ConstantReferenceId>,
18
19
  diagnostics: Vec<Diagnostic>,
19
20
  }
21
+ assert_mem_size!(Document, 176);
20
22
 
21
23
  impl Document {
22
24
  #[must_use]
@@ -1,3 +1,4 @@
1
+ use crate::assert_mem_size;
1
2
  use line_index::WideEncoding;
2
3
 
3
4
  #[derive(Default, Debug)]
@@ -7,6 +8,7 @@ pub enum Encoding {
7
8
  Utf16,
8
9
  Utf32,
9
10
  }
11
+ assert_mem_size!(Encoding, 1);
10
12
 
11
13
  impl Encoding {
12
14
  /// Transform the LSP selected encoding into the expected `WideEncoding` for converting code units with the
@@ -2,6 +2,7 @@ use std::collections::HashSet;
2
2
  use std::collections::hash_map::Entry;
3
3
  use std::path::PathBuf;
4
4
 
5
+ use crate::assert_mem_size;
5
6
  use crate::diagnostic::Diagnostic;
6
7
  use crate::indexing::local_graph::LocalGraph;
7
8
  use crate::model::built_in::{OBJECT_ID, add_built_in_data};
@@ -28,6 +29,7 @@ pub enum NameDependent {
28
29
  /// This name's `nesting` is the key name — reference-only dependency.
29
30
  NestedName(NameId),
30
31
  }
32
+ assert_mem_size!(NameDependent, 16);
31
33
 
32
34
  /// Items processed by the unified invalidation worklist.
33
35
  enum InvalidationItem {
@@ -38,6 +40,7 @@ enum InvalidationItem {
38
40
  /// Ancestor context changed — unresolve references under this name but keep the name resolved.
39
41
  References(NameId),
40
42
  }
43
+ assert_mem_size!(InvalidationItem, 16);
41
44
 
42
45
  /// A work item produced by graph mutations (update/delete) that needs resolution.
43
46
  #[derive(Debug)]
@@ -49,6 +52,7 @@ pub enum Unit {
49
52
  /// A declaration whose ancestors need re-linearization
50
53
  Ancestors(DeclarationId),
51
54
  }
55
+ assert_mem_size!(Unit, 16);
52
56
 
53
57
  // The `Graph` is the global representation of the entire Ruby codebase. It contains all declarations and their
54
58
  // relationships
@@ -84,6 +88,7 @@ pub struct Graph {
84
88
  /// Paths to exclude from file discovery during indexing.
85
89
  excluded_paths: HashSet<PathBuf>,
86
90
  }
91
+ assert_mem_size!(Graph, 336);
87
92
 
88
93
  impl Graph {
89
94
  #[must_use]
@@ -306,9 +311,6 @@ impl Graph {
306
311
  self.definition_to_declaration_id(self.definitions.get(&definition_id).unwrap())
307
312
  }
308
313
 
309
- /// # Panics
310
- ///
311
- /// Panics if the definition is not found
312
314
  #[must_use]
313
315
  pub fn definition_to_declaration_id(&self, definition: &Definition) -> Option<&DeclarationId> {
314
316
  let (nesting_name_id, member_str_id) = match definition {
@@ -442,20 +444,21 @@ impl Graph {
442
444
  }
443
445
 
444
446
  /// Looks up the declaration for a `SelfReceiver` method/alias through the singleton class.
447
+ ///
448
+ /// Returns `None` when the owner cannot be resolved to a namespace with a singleton class. This
449
+ /// can happen when the enclosing construct resolved to a non-namespace declaration (e.g. a
450
+ /// constant or constant alias that a same-named `class`/`module` reopened without promotion), in
451
+ /// which case the method has no owning declaration.
445
452
  fn find_self_receiver_declaration(&self, def_id: DefinitionId, member_str_id: StringId) -> Option<&DeclarationId> {
446
453
  let owner_decl_id = self.definition_id_to_declaration_id(def_id)?;
447
454
  let singleton_id = self
448
455
  .declarations
449
- .get(owner_decl_id)
450
- .unwrap()
451
- .as_namespace()
452
- .unwrap()
456
+ .get(owner_decl_id)?
457
+ .as_namespace()?
453
458
  .singleton_class()?;
454
459
  self.declarations
455
- .get(singleton_id)
456
- .unwrap()
457
- .as_namespace()
458
- .unwrap()
460
+ .get(singleton_id)?
461
+ .as_namespace()?
459
462
  .member(&member_str_id)
460
463
  }
461
464
 
@@ -1540,8 +1543,8 @@ mod tests {
1540
1543
  use crate::model::declaration::Ancestors;
1541
1544
  use crate::test_utils::GraphTest;
1542
1545
  use crate::{
1543
- assert_declaration_does_not_exist, assert_dependents, assert_descendants, assert_members_eq,
1544
- assert_no_diagnostics, assert_no_members,
1546
+ assert_declaration_does_not_exist, assert_declaration_kind_eq, assert_dependents, assert_descendants,
1547
+ assert_members_eq, assert_no_diagnostics, assert_no_members,
1545
1548
  };
1546
1549
 
1547
1550
  #[test]
@@ -1563,6 +1566,41 @@ mod tests {
1563
1566
  );
1564
1567
  }
1565
1568
 
1569
+ #[test]
1570
+ fn singleton_method_in_non_namespace_owner_does_not_panic() {
1571
+ // `Aliased = Bar` assigns a constant to another constant, producing a (non-promotable)
1572
+ // `ConstantAlias` declaration. Reopening it with `class Aliased` is valid Ruby (it reopens
1573
+ // `Bar`), but the class definition is attached to the existing `ConstantAlias` declaration
1574
+ // without promoting it to a namespace. A `def self.foo` inside then has a `SelfReceiver`
1575
+ // owner whose declaration is not a namespace. Resolving that definition to its declaration
1576
+ // must return `None` rather than panicking.
1577
+ let mut context = GraphTest::new();
1578
+
1579
+ context.index_uri(
1580
+ "file:///foo.rb",
1581
+ "
1582
+ class Bar
1583
+ end
1584
+
1585
+ Aliased = Bar
1586
+
1587
+ class Aliased
1588
+ def self.foo; end
1589
+ end
1590
+ ",
1591
+ );
1592
+ context.resolve();
1593
+
1594
+ // The declaration stays a non-namespace constant alias.
1595
+ assert_declaration_kind_eq!(context, "Aliased", "ConstantAlias");
1596
+
1597
+ // Mirrors what consumers like the DOT exporter do: resolve every definition to its
1598
+ // declaration. This previously panicked on the singleton method's non-namespace owner.
1599
+ for definition in context.graph().definitions().values() {
1600
+ let _ = context.graph().definition_to_declaration_id(definition);
1601
+ }
1602
+ }
1603
+
1566
1604
  #[test]
1567
1605
  fn deleting_file_triggers_name_dependent_cleanup() {
1568
1606
  let mut context = GraphTest::new();
@@ -6,10 +6,13 @@ use std::{
6
6
  hash::{BuildHasher, Hasher},
7
7
  };
8
8
 
9
+ use crate::assert_mem_size;
10
+
9
11
  #[derive(Default)]
10
12
  pub struct IdentityHasher {
11
13
  hash: u64,
12
14
  }
15
+ assert_mem_size!(IdentityHasher, 8);
13
16
 
14
17
  impl Hasher for IdentityHasher {
15
18
  fn write(&mut self, _bytes: &[u8]) {
@@ -1,3 +1,5 @@
1
+ use crate::assert_mem_size;
2
+
1
3
  /// A Ruby keyword with its documentation.
2
4
  #[derive(Debug)]
3
5
  pub struct Keyword {
@@ -6,6 +8,7 @@ pub struct Keyword {
6
8
  /// Documentation string for hover display
7
9
  documentation: &'static str,
8
10
  }
11
+ assert_mem_size!(Keyword, 32);
9
12
 
10
13
  impl Keyword {
11
14
  #[must_use]
@@ -141,6 +141,7 @@ pub struct ResolvedName {
141
141
  name: Name,
142
142
  declaration_id: DeclarationId,
143
143
  }
144
+ assert_mem_size!(ResolvedName, 48);
144
145
 
145
146
  impl ResolvedName {
146
147
  #[must_use]
@@ -173,6 +174,7 @@ pub enum NameRef {
173
174
  /// This name has been resolved to an existing declaration
174
175
  Resolved(Box<ResolvedName>),
175
176
  }
177
+ assert_mem_size!(NameRef, 16);
176
178
 
177
179
  impl NameRef {
178
180
  #[must_use]
@@ -1,3 +1,4 @@
1
+ use crate::assert_mem_size;
1
2
  use std::ops::Deref;
2
3
 
3
4
  /// A reference-counted string used in the graph.
@@ -11,6 +12,7 @@ pub struct StringRef {
11
12
  value: String,
12
13
  ref_count: u32,
13
14
  }
15
+ assert_mem_size!(StringRef, 32);
14
16
 
15
17
  impl StringRef {
16
18
  #[must_use]
@@ -1,6 +1,8 @@
1
1
  use core::fmt;
2
2
  use std::fmt::Display;
3
3
 
4
+ use crate::assert_mem_size;
5
+
4
6
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
5
7
  pub enum Visibility {
6
8
  Public,
@@ -8,6 +10,7 @@ pub enum Visibility {
8
10
  Private,
9
11
  ModuleFunction,
10
12
  }
13
+ assert_mem_size!(Visibility, 1);
11
14
 
12
15
  impl Visibility {
13
16
  /// Parse a visibility from a string.
@@ -219,6 +219,7 @@ impl OperationApplier {
219
219
  op.str_id,
220
220
  op.uri_id,
221
221
  op.offset,
222
+ op.name_offset,
222
223
  op.comments,
223
224
  op.flags,
224
225
  lexical_nesting_id,
@@ -152,6 +152,7 @@ pub struct EnterMethod {
152
152
  pub str_id: StringId,
153
153
  pub uri_id: UriId,
154
154
  pub offset: Offset,
155
+ pub name_offset: Offset,
155
156
  pub comments: Box<[Comment]>,
156
157
  pub flags: DefinitionFlags,
157
158
  pub signatures: Signatures,
@@ -1524,6 +1524,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
1524
1524
  let name = Self::location_to_string(&node.name_loc());
1525
1525
  let str_id = self.intern_string(format!("{name}()"));
1526
1526
  let offset = Offset::from_prism_location(&node.location());
1527
+ let name_offset = Offset::from_prism_location(&node.name_loc());
1527
1528
  let parameters = self.collect_parameters(node);
1528
1529
  let is_singleton = node.receiver().is_some();
1529
1530
 
@@ -1586,6 +1587,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
1586
1587
  str_id,
1587
1588
  uri_id: self.uri_id,
1588
1589
  offset: offset.clone(),
1590
+ name_offset: name_offset.clone(),
1589
1591
  comments: comments.clone(),
1590
1592
  flags: flags.clone(),
1591
1593
  signatures: Signatures::Simple(parameters.clone().into_boxed_slice()),
@@ -1604,6 +1606,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
1604
1606
  str_id,
1605
1607
  uri_id: self.uri_id,
1606
1608
  offset: offset.clone(),
1609
+ name_offset: name_offset.clone(),
1607
1610
  comments,
1608
1611
  flags,
1609
1612
  signatures: Signatures::Simple(parameters.into_boxed_slice()),
@@ -1638,6 +1641,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
1638
1641
  str_id,
1639
1642
  uri_id: self.uri_id,
1640
1643
  offset: offset.clone(),
1644
+ name_offset,
1641
1645
  comments,
1642
1646
  flags,
1643
1647
  signatures: Signatures::Simple(parameters.into_boxed_slice()),