rubydex 0.1.0.beta11 → 0.1.0.beta13

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +23 -23
  3. data/README.md +125 -125
  4. data/THIRD_PARTY_LICENSES.html +2018 -945
  5. data/exe/rdx +47 -47
  6. data/ext/rubydex/declaration.c +453 -388
  7. data/ext/rubydex/declaration.h +23 -23
  8. data/ext/rubydex/definition.c +284 -197
  9. data/ext/rubydex/definition.h +28 -28
  10. data/ext/rubydex/diagnostic.c +6 -6
  11. data/ext/rubydex/diagnostic.h +11 -11
  12. data/ext/rubydex/document.c +97 -98
  13. data/ext/rubydex/document.h +10 -10
  14. data/ext/rubydex/extconf.rb +146 -127
  15. data/ext/rubydex/graph.c +701 -512
  16. data/ext/rubydex/graph.h +10 -10
  17. data/ext/rubydex/handle.h +44 -44
  18. data/ext/rubydex/location.c +22 -22
  19. data/ext/rubydex/location.h +15 -15
  20. data/ext/rubydex/reference.c +123 -104
  21. data/ext/rubydex/reference.h +15 -16
  22. data/ext/rubydex/rubydex.c +22 -22
  23. data/ext/rubydex/utils.c +108 -86
  24. data/ext/rubydex/utils.h +34 -28
  25. data/lib/rubydex/comment.rb +17 -17
  26. data/lib/rubydex/declaration.rb +11 -0
  27. data/lib/rubydex/diagnostic.rb +21 -21
  28. data/lib/rubydex/failures.rb +15 -15
  29. data/lib/rubydex/graph.rb +98 -92
  30. data/lib/rubydex/keyword.rb +17 -0
  31. data/lib/rubydex/keyword_parameter.rb +13 -0
  32. data/lib/rubydex/location.rb +90 -90
  33. data/lib/rubydex/mixin.rb +22 -0
  34. data/lib/rubydex/version.rb +5 -5
  35. data/lib/rubydex.rb +24 -20
  36. data/rbi/rubydex.rbi +425 -310
  37. data/rust/Cargo.lock +1851 -1851
  38. data/rust/Cargo.toml +29 -29
  39. data/rust/about.toml +10 -10
  40. data/rust/{about.hbs → about_templates/about.hbs} +81 -78
  41. data/rust/about_templates/mingw_licenses.hbs +1071 -0
  42. data/rust/rubydex/Cargo.toml +42 -42
  43. data/rust/rubydex/src/compile_assertions.rs +13 -13
  44. data/rust/rubydex/src/diagnostic.rs +110 -109
  45. data/rust/rubydex/src/errors.rs +28 -28
  46. data/rust/rubydex/src/indexing/local_graph.rs +224 -224
  47. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -1554
  48. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -6753
  49. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  50. data/rust/rubydex/src/indexing.rs +210 -210
  51. data/rust/rubydex/src/integrity.rs +279 -278
  52. data/rust/rubydex/src/job_queue.rs +199 -205
  53. data/rust/rubydex/src/lib.rs +17 -17
  54. data/rust/rubydex/src/listing.rs +371 -272
  55. data/rust/rubydex/src/main.rs +160 -160
  56. data/rust/rubydex/src/model/built_in.rs +83 -0
  57. data/rust/rubydex/src/model/comment.rs +24 -24
  58. data/rust/rubydex/src/model/declaration.rs +679 -588
  59. data/rust/rubydex/src/model/definitions.rs +1682 -1602
  60. data/rust/rubydex/src/model/document.rs +222 -252
  61. data/rust/rubydex/src/model/encoding.rs +22 -22
  62. data/rust/rubydex/src/model/graph.rs +3782 -3556
  63. data/rust/rubydex/src/model/id.rs +110 -110
  64. data/rust/rubydex/src/model/identity_maps.rs +58 -58
  65. data/rust/rubydex/src/model/ids.rs +60 -38
  66. data/rust/rubydex/src/model/keywords.rs +256 -256
  67. data/rust/rubydex/src/model/name.rs +298 -298
  68. data/rust/rubydex/src/model/references.rs +111 -111
  69. data/rust/rubydex/src/model/string_ref.rs +50 -50
  70. data/rust/rubydex/src/model/visibility.rs +41 -41
  71. data/rust/rubydex/src/model.rs +15 -14
  72. data/rust/rubydex/src/offset.rs +147 -147
  73. data/rust/rubydex/src/position.rs +6 -6
  74. data/rust/rubydex/src/query.rs +1841 -1700
  75. data/rust/rubydex/src/resolution.rs +1852 -5895
  76. data/rust/rubydex/src/resolution_tests.rs +4701 -0
  77. data/rust/rubydex/src/stats/memory.rs +71 -71
  78. data/rust/rubydex/src/stats/orphan_report.rs +264 -263
  79. data/rust/rubydex/src/stats/timer.rs +127 -127
  80. data/rust/rubydex/src/stats.rs +11 -11
  81. data/rust/rubydex/src/test_utils/context.rs +226 -226
  82. data/rust/rubydex/src/test_utils/graph_test.rs +730 -679
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -602
  84. data/rust/rubydex/src/test_utils.rs +52 -52
  85. data/rust/rubydex/src/visualization/dot.rs +192 -176
  86. data/rust/rubydex/src/visualization.rs +6 -6
  87. data/rust/rubydex/tests/cli.rs +185 -167
  88. data/rust/rubydex-mcp/Cargo.toml +28 -28
  89. data/rust/rubydex-mcp/src/main.rs +48 -48
  90. data/rust/rubydex-mcp/src/server.rs +1145 -1145
  91. data/rust/rubydex-mcp/src/tools.rs +49 -49
  92. data/rust/rubydex-mcp/tests/mcp.rs +302 -302
  93. data/rust/rubydex-sys/Cargo.toml +20 -20
  94. data/rust/rubydex-sys/build.rs +14 -14
  95. data/rust/rubydex-sys/cbindgen.toml +12 -12
  96. data/rust/rubydex-sys/src/declaration_api.rs +485 -469
  97. data/rust/rubydex-sys/src/definition_api.rs +443 -352
  98. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -99
  99. data/rust/rubydex-sys/src/document_api.rs +85 -54
  100. data/rust/rubydex-sys/src/graph_api.rs +1017 -700
  101. data/rust/rubydex-sys/src/lib.rs +79 -9
  102. data/rust/rubydex-sys/src/location_api.rs +79 -79
  103. data/rust/rubydex-sys/src/name_api.rs +187 -135
  104. data/rust/rubydex-sys/src/reference_api.rs +267 -195
  105. data/rust/rubydex-sys/src/utils.rs +70 -70
  106. data/rust/rustfmt.toml +2 -2
  107. metadata +16 -9
  108. data/lib/rubydex/librubydex_sys.so +0 -0
@@ -1,9 +1,79 @@
1
- pub mod declaration_api;
2
- pub mod definition_api;
3
- pub mod diagnostic_api;
4
- pub mod document_api;
5
- pub mod graph_api;
6
- pub mod location_api;
7
- pub mod name_api;
8
- pub mod reference_api;
9
- pub mod utils;
1
+ /// Helper macro to generate all required functions for an iterator. We use iterators to go over any collection of data
2
+ /// that exists on the Rust side (e.g.: definitions, documents, declarations, references). The goal is to avoid eager
3
+ /// allocation of large collections when possible.
4
+ ///
5
+ /// Note: structs must be defined manually so that the cbindgen can see them. The actual methods are not extern "C" and
6
+ /// so they can be expanded from the macro.
7
+ ///
8
+ /// # Example
9
+ ///
10
+ /// ```ignore
11
+ /// pub struct FoosIter {
12
+ /// entries: Box<[Foo]>,
13
+ /// index: usize,
14
+ /// }
15
+ ///
16
+ /// iterator!(FoosIter, entries: Foo);
17
+ /// ```
18
+ macro_rules! iterator {
19
+ ($name:ident, $field:ident : $entry:ty) => {
20
+ impl $name {
21
+ #[must_use]
22
+ pub fn new($field: Box<[$entry]>) -> *mut $name {
23
+ Box::into_raw(Box::new($name { $field, index: 0 }))
24
+ }
25
+
26
+ /// # Safety
27
+ /// `iter` must be a valid pointer returned by `new`, or null.
28
+ pub unsafe fn len(iter: *const Self) -> usize {
29
+ if iter.is_null() {
30
+ return 0;
31
+ }
32
+ unsafe { (&*iter).$field.len() }
33
+ }
34
+
35
+ /// # Safety
36
+ /// - `iter` must be a valid pointer returned by `new`, or null.
37
+ /// - `out` must be a valid, writable pointer, or null.
38
+ pub unsafe fn next(iter: *mut Self, out: *mut $entry) -> bool {
39
+ if iter.is_null() || out.is_null() {
40
+ return false;
41
+ }
42
+
43
+ let it = unsafe { &mut *iter };
44
+ if it.index >= it.$field.len() {
45
+ return false;
46
+ }
47
+
48
+ let entry = it.$field[it.index];
49
+ it.index += 1;
50
+ unsafe {
51
+ *out = entry;
52
+ }
53
+
54
+ true
55
+ }
56
+
57
+ /// # Safety
58
+ /// `iter` must be a pointer returned by `new` (or null). Must not be used after.
59
+ pub unsafe fn free(iter: *mut Self) {
60
+ if iter.is_null() {
61
+ return;
62
+ }
63
+ unsafe {
64
+ let _ = Box::from_raw(iter);
65
+ }
66
+ }
67
+ }
68
+ };
69
+ }
70
+
71
+ pub mod declaration_api;
72
+ pub mod definition_api;
73
+ pub mod diagnostic_api;
74
+ pub mod document_api;
75
+ pub mod graph_api;
76
+ pub mod location_api;
77
+ pub mod name_api;
78
+ pub mod reference_api;
79
+ pub mod utils;
@@ -1,79 +1,79 @@
1
- //! Location-related C API and structs
2
-
3
- use libc::c_char;
4
- use rubydex::model::document::Document;
5
- use rubydex::model::graph::Graph;
6
- use rubydex::offset::Offset;
7
- use std::ffi::CString;
8
-
9
- /// C-compatible struct representing a definition location with offsets and line/column positions.
10
- #[repr(C)]
11
- #[derive(Debug, Clone)]
12
- pub struct Location {
13
- pub uri: *const c_char,
14
- pub start_line: u32,
15
- pub end_line: u32,
16
- pub start_column: u32,
17
- pub end_column: u32,
18
- }
19
-
20
- /// Helper to create a location for a given URI and byte-offset range.
21
- /// Allocates and returns a pointer to `Location`. Caller must free with `rdx_location_free`.
22
- ///
23
- /// # Panics
24
- ///
25
- /// - If the URI cannot be converted to a file path.
26
- /// - If the file cannot be read.
27
- /// - If the offset cannot be converted to a position.
28
- #[must_use]
29
- pub(crate) fn create_location_for_uri_and_offset(graph: &Graph, document: &Document, offset: &Offset) -> *mut Location {
30
- let line_index = document.line_index();
31
- let start_pos = line_index.line_col(offset.start().into());
32
- let end_pos = line_index.line_col(offset.end().into());
33
-
34
- let loc = if let Some(wide_encoding) = graph.encoding().to_wide() {
35
- let wide_start_pos = line_index.to_wide(wide_encoding, start_pos).unwrap();
36
- let wide_end_pos = line_index.to_wide(wide_encoding, end_pos).unwrap();
37
-
38
- Location {
39
- uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
40
- start_line: wide_start_pos.line,
41
- end_line: wide_end_pos.line,
42
- start_column: wide_start_pos.col,
43
- end_column: wide_end_pos.col,
44
- }
45
- } else {
46
- Location {
47
- uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
48
- start_line: start_pos.line,
49
- end_line: end_pos.line,
50
- start_column: start_pos.col,
51
- end_column: end_pos.col,
52
- }
53
- };
54
-
55
- Box::into_raw(Box::new(loc))
56
- }
57
-
58
- /// Frees a `Location` struct and its owned inner strings.
59
- ///
60
- /// # Safety
61
- ///
62
- /// - `ptr` must be a valid pointer previously returned by `create_location_for_uri_and_offset`.
63
- /// - `ptr` must not be used after being freed.
64
- #[unsafe(no_mangle)]
65
- pub unsafe extern "C" fn rdx_location_free(ptr: *mut Location) {
66
- if ptr.is_null() {
67
- return;
68
- }
69
- unsafe {
70
- // Take ownership of the box so we can free inner allocations first
71
- let boxed = Box::from_raw(ptr);
72
-
73
- if !boxed.uri.is_null() {
74
- let _ = CString::from_raw(boxed.uri.cast_mut());
75
- }
76
-
77
- // Box drops here, freeing the struct memory
78
- }
79
- }
1
+ //! Location-related C API and structs
2
+
3
+ use libc::c_char;
4
+ use rubydex::model::document::Document;
5
+ use rubydex::model::graph::Graph;
6
+ use rubydex::offset::Offset;
7
+ use std::ffi::CString;
8
+
9
+ /// C-compatible struct representing a definition location with offsets and line/column positions.
10
+ #[repr(C)]
11
+ #[derive(Debug, Clone)]
12
+ pub struct Location {
13
+ pub uri: *const c_char,
14
+ pub start_line: u32,
15
+ pub end_line: u32,
16
+ pub start_column: u32,
17
+ pub end_column: u32,
18
+ }
19
+
20
+ /// Helper to create a location for a given URI and byte-offset range.
21
+ /// Allocates and returns a pointer to `Location`. Caller must free with `rdx_location_free`.
22
+ ///
23
+ /// # Panics
24
+ ///
25
+ /// - If the URI cannot be converted to a file path.
26
+ /// - If the file cannot be read.
27
+ /// - If the offset cannot be converted to a position.
28
+ #[must_use]
29
+ pub(crate) fn create_location_for_uri_and_offset(graph: &Graph, document: &Document, offset: &Offset) -> *mut Location {
30
+ let line_index = document.line_index();
31
+ let start_pos = line_index.line_col(offset.start().into());
32
+ let end_pos = line_index.line_col(offset.end().into());
33
+
34
+ let loc = if let Some(wide_encoding) = graph.encoding().to_wide() {
35
+ let wide_start_pos = line_index.to_wide(wide_encoding, start_pos).unwrap();
36
+ let wide_end_pos = line_index.to_wide(wide_encoding, end_pos).unwrap();
37
+
38
+ Location {
39
+ uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
40
+ start_line: wide_start_pos.line,
41
+ end_line: wide_end_pos.line,
42
+ start_column: wide_start_pos.col,
43
+ end_column: wide_end_pos.col,
44
+ }
45
+ } else {
46
+ Location {
47
+ uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
48
+ start_line: start_pos.line,
49
+ end_line: end_pos.line,
50
+ start_column: start_pos.col,
51
+ end_column: end_pos.col,
52
+ }
53
+ };
54
+
55
+ Box::into_raw(Box::new(loc))
56
+ }
57
+
58
+ /// Frees a `Location` struct and its owned inner strings.
59
+ ///
60
+ /// # Safety
61
+ ///
62
+ /// - `ptr` must be a valid pointer previously returned by `create_location_for_uri_and_offset`.
63
+ /// - `ptr` must not be used after being freed.
64
+ #[unsafe(no_mangle)]
65
+ pub unsafe extern "C" fn rdx_location_free(ptr: *mut Location) {
66
+ if ptr.is_null() {
67
+ return;
68
+ }
69
+ unsafe {
70
+ // Take ownership of the box so we can free inner allocations first
71
+ let boxed = Box::from_raw(ptr);
72
+
73
+ if !boxed.uri.is_null() {
74
+ let _ = CString::from_raw(boxed.uri.cast_mut());
75
+ }
76
+
77
+ // Box drops here, freeing the struct memory
78
+ }
79
+ }
@@ -1,135 +1,187 @@
1
- use rubydex::model::{
2
- graph::Graph,
3
- ids::NameId,
4
- name::{Name, ParentScope},
5
- };
6
-
7
- /// Takes a constant name and a nesting stack (e.g.: `["Foo", "Bar::Baz", "Qux"]`) and transforms it into a `NameId`,
8
- /// registering each required part in the graph. Returns the `NameId` and a list of name ids that need to be untracked
9
- /// afterwards
10
- ///
11
- /// # Panics
12
- ///
13
- /// Should not panic because `const_name` will always be turned into a name
14
- pub fn nesting_stack_to_name_id(graph: &mut Graph, const_name: &str, nesting: Vec<String>) -> (NameId, Vec<NameId>) {
15
- let mut current_nesting = None;
16
- let mut current_name = ParentScope::None;
17
- let mut names_to_untrack = Vec::new();
18
-
19
- for entry in nesting {
20
- for part in entry.split("::").map(String::from) {
21
- if part.is_empty() {
22
- current_name = ParentScope::TopLevel;
23
- continue;
24
- }
25
-
26
- let str_id = graph.intern_string(part);
27
- let name_id = graph.add_name(Name::new(str_id, current_name, current_nesting));
28
- names_to_untrack.push(name_id);
29
- current_name = ParentScope::Some(name_id);
30
- }
31
-
32
- current_nesting = current_name.map_or(None, |id| Some(*id));
33
- current_name = ParentScope::None;
34
- }
35
-
36
- for part in const_name.split("::").map(String::from) {
37
- if part.is_empty() {
38
- current_name = ParentScope::TopLevel;
39
- continue;
40
- }
41
-
42
- let str_id = graph.intern_string(part);
43
- let name_id = graph.add_name(Name::new(str_id, current_name, current_nesting));
44
- names_to_untrack.push(name_id);
45
- current_name = ParentScope::Some(name_id);
46
- }
47
-
48
- (
49
- current_name.expect("The NameId cannot be None since it contains at least `const_name`"),
50
- names_to_untrack,
51
- )
52
- }
53
-
54
- #[cfg(test)]
55
- mod tests {
56
- use rubydex::model::ids::StringId;
57
-
58
- use super::*;
59
-
60
- #[test]
61
- fn nesting_is_converted_to_name_id() {
62
- let mut graph = Graph::new();
63
-
64
- let (name_id, _) = nesting_stack_to_name_id(
65
- &mut graph,
66
- "Some::CONST",
67
- vec!["Foo".into(), "Bar::Zip".into(), "Qux".into()],
68
- );
69
-
70
- let const_name = graph.names().get(&name_id).unwrap();
71
- assert_eq!(StringId::from("CONST"), *const_name.str());
72
-
73
- let some_name = graph
74
- .names()
75
- .get(&const_name.parent_scope().expect("Parent scope should exist"))
76
- .unwrap();
77
- assert_eq!(StringId::from("Some"), *some_name.str());
78
- assert_eq!(const_name.nesting(), some_name.nesting());
79
-
80
- let qux_name = graph.names().get(&some_name.nesting().unwrap()).unwrap();
81
- assert_eq!(StringId::from("Qux"), *qux_name.str());
82
- assert!(qux_name.parent_scope().is_none());
83
-
84
- let zip_name = graph.names().get(&qux_name.nesting().unwrap()).unwrap();
85
- assert_eq!(StringId::from("Zip"), *zip_name.str());
86
-
87
- let bar_name = graph
88
- .names()
89
- .get(&zip_name.parent_scope().expect("Parent scope should exist"))
90
- .unwrap();
91
- assert_eq!(StringId::from("Bar"), *bar_name.str());
92
- assert_eq!(zip_name.nesting(), bar_name.nesting());
93
-
94
- let foo_name = graph.names().get(&bar_name.nesting().unwrap()).unwrap();
95
- assert_eq!(StringId::from("Foo"), *foo_name.str());
96
- assert!(foo_name.parent_scope().is_none());
97
- assert!(foo_name.nesting().is_none());
98
- }
99
-
100
- #[test]
101
- fn top_level_reference_is_converted_to_name_id() {
102
- let mut graph = Graph::new();
103
-
104
- let (name_id, _) = nesting_stack_to_name_id(&mut graph, "::CONST", vec!["Foo".into()]);
105
-
106
- let const_name = graph.names().get(&name_id).unwrap();
107
- assert_eq!(StringId::from("CONST"), *const_name.str());
108
- assert!(const_name.parent_scope().is_top_level());
109
-
110
- let foo_name = graph.names().get(&const_name.nesting().unwrap()).unwrap();
111
- assert_eq!(StringId::from("Foo"), *foo_name.str());
112
- assert!(foo_name.nesting().is_none());
113
- assert!(foo_name.parent_scope().is_none());
114
- }
115
-
116
- #[test]
117
- fn top_level_nesting_is_converted_to_name_id() {
118
- let mut graph = Graph::new();
119
-
120
- let (name_id, _) = nesting_stack_to_name_id(&mut graph, "CONST", vec!["Foo".into(), "::Bar".into()]);
121
-
122
- let const_name = graph.names().get(&name_id).unwrap();
123
- assert_eq!(StringId::from("CONST"), *const_name.str());
124
- assert!(const_name.parent_scope().is_none());
125
-
126
- let bar_name = graph.names().get(&const_name.nesting().unwrap()).unwrap();
127
- assert_eq!(StringId::from("Bar"), *bar_name.str());
128
- assert!(bar_name.parent_scope().is_top_level());
129
-
130
- let foo_name = graph.names().get(&bar_name.nesting().unwrap()).unwrap();
131
- assert_eq!(StringId::from("Foo"), *foo_name.str());
132
- assert!(foo_name.parent_scope().is_none());
133
- assert!(foo_name.nesting().is_none());
134
- }
135
- }
1
+ use rubydex::model::{
2
+ graph::Graph,
3
+ ids::NameId,
4
+ name::{Name, ParentScope},
5
+ };
6
+
7
+ /// Takes a constant name and a nesting stack (e.g.: `["Foo", "Bar::Baz", "Qux"]`) and transforms it into a `NameId`,
8
+ /// registering each required part in the graph. Returns the `NameId` and a list of name ids that need to be untracked
9
+ /// afterwards. Returns `None` if the constant name contains no valid identifier parts (e.g.: `""`, `"::"`, `"Foo::"`).
10
+ pub fn nesting_stack_to_name_id(
11
+ graph: &mut Graph,
12
+ const_name: &str,
13
+ nesting: Vec<String>,
14
+ ) -> Option<(NameId, Vec<NameId>)> {
15
+ let mut current_nesting = None;
16
+ let mut current_name = ParentScope::None;
17
+ let mut names_to_untrack = Vec::new();
18
+
19
+ for entry in nesting {
20
+ process_qualified_name(
21
+ graph,
22
+ &entry,
23
+ &mut current_name,
24
+ &mut current_nesting,
25
+ &mut names_to_untrack,
26
+ );
27
+ current_nesting = current_name.as_ref().copied();
28
+ current_name = ParentScope::None;
29
+ }
30
+
31
+ process_qualified_name(
32
+ graph,
33
+ const_name,
34
+ &mut current_name,
35
+ &mut current_nesting,
36
+ &mut names_to_untrack,
37
+ );
38
+
39
+ let (ParentScope::Some(name_id) | ParentScope::Attached(name_id)) = current_name else {
40
+ return None;
41
+ };
42
+
43
+ Some((name_id, names_to_untrack))
44
+ }
45
+
46
+ /// Processes a qualified name (e.g., `"Foo::Bar"` or `"<Foo>"`) by splitting on `"::"` and registering each part in the
47
+ /// graph. Singleton class names (starting with `<`) use `ParentScope::Attached` and `nesting=None`, matching how the
48
+ /// indexer creates them. When a singleton is the first part (i.e., `current_name` has no parent), `current_nesting` is
49
+ /// used as the attachment point.
50
+ fn process_qualified_name(
51
+ graph: &mut Graph,
52
+ qualified_name: &str,
53
+ current_name: &mut ParentScope,
54
+ current_nesting: &mut Option<NameId>,
55
+ names_to_untrack: &mut Vec<NameId>,
56
+ ) {
57
+ for part in qualified_name.split("::") {
58
+ if part.is_empty() {
59
+ *current_name = ParentScope::TopLevel;
60
+ continue;
61
+ }
62
+
63
+ let (parent_scope, nesting_for_part) = if part.starts_with('<') {
64
+ let attached = match *current_name {
65
+ ParentScope::Some(id) | ParentScope::Attached(id) => ParentScope::Attached(id),
66
+ _ => current_nesting.map_or(ParentScope::None, ParentScope::Attached),
67
+ };
68
+
69
+ (attached, None)
70
+ } else {
71
+ (*current_name, *current_nesting)
72
+ };
73
+
74
+ let str_id = graph.intern_string(part.to_owned());
75
+ let name_id = graph.add_name(Name::new(str_id, parent_scope, nesting_for_part));
76
+ names_to_untrack.push(name_id);
77
+ *current_name = ParentScope::Some(name_id);
78
+ }
79
+ }
80
+
81
+ #[cfg(test)]
82
+ mod tests {
83
+ use rubydex::model::ids::StringId;
84
+
85
+ use super::*;
86
+
87
+ #[test]
88
+ fn nesting_is_converted_to_name_id() {
89
+ let mut graph = Graph::new();
90
+
91
+ let (name_id, _) = nesting_stack_to_name_id(
92
+ &mut graph,
93
+ "Some::CONST",
94
+ vec!["Foo".into(), "Bar::Zip".into(), "Qux".into()],
95
+ )
96
+ .unwrap();
97
+
98
+ let const_name = graph.names().get(&name_id).unwrap();
99
+ assert_eq!(StringId::from("CONST"), *const_name.str());
100
+
101
+ let some_name = graph
102
+ .names()
103
+ .get(&const_name.parent_scope().expect("Parent scope should exist"))
104
+ .unwrap();
105
+ assert_eq!(StringId::from("Some"), *some_name.str());
106
+ assert_eq!(const_name.nesting(), some_name.nesting());
107
+
108
+ let qux_name = graph.names().get(&some_name.nesting().unwrap()).unwrap();
109
+ assert_eq!(StringId::from("Qux"), *qux_name.str());
110
+ assert!(qux_name.parent_scope().is_none());
111
+
112
+ let zip_name = graph.names().get(&qux_name.nesting().unwrap()).unwrap();
113
+ assert_eq!(StringId::from("Zip"), *zip_name.str());
114
+
115
+ let bar_name = graph
116
+ .names()
117
+ .get(&zip_name.parent_scope().expect("Parent scope should exist"))
118
+ .unwrap();
119
+ assert_eq!(StringId::from("Bar"), *bar_name.str());
120
+ assert_eq!(zip_name.nesting(), bar_name.nesting());
121
+
122
+ let foo_name = graph.names().get(&bar_name.nesting().unwrap()).unwrap();
123
+ assert_eq!(StringId::from("Foo"), *foo_name.str());
124
+ assert!(foo_name.parent_scope().is_none());
125
+ assert!(foo_name.nesting().is_none());
126
+ }
127
+
128
+ #[test]
129
+ fn top_level_reference_is_converted_to_name_id() {
130
+ let mut graph = Graph::new();
131
+
132
+ let (name_id, _) = nesting_stack_to_name_id(&mut graph, "::CONST", vec!["Foo".into()]).unwrap();
133
+
134
+ let const_name = graph.names().get(&name_id).unwrap();
135
+ assert_eq!(StringId::from("CONST"), *const_name.str());
136
+ assert!(const_name.parent_scope().is_top_level());
137
+
138
+ let foo_name = graph.names().get(&const_name.nesting().unwrap()).unwrap();
139
+ assert_eq!(StringId::from("Foo"), *foo_name.str());
140
+ assert!(foo_name.nesting().is_none());
141
+ assert!(foo_name.parent_scope().is_none());
142
+ }
143
+
144
+ #[test]
145
+ fn singleton_class_names_use_attached_parent_scope() {
146
+ let mut graph = Graph::new();
147
+
148
+ let (name_id, _) = nesting_stack_to_name_id(&mut graph, "CONST", vec!["Foo".into(), "<Foo>".into()]).unwrap();
149
+
150
+ let const_name = graph.names().get(&name_id).unwrap();
151
+ assert_eq!(StringId::from("CONST"), *const_name.str());
152
+
153
+ // The nesting should be <Foo> with an Attached parent scope
154
+ let singleton_name = graph.names().get(&const_name.nesting().unwrap()).unwrap();
155
+ assert_eq!(StringId::from("<Foo>"), *singleton_name.str());
156
+ assert!(
157
+ matches!(singleton_name.parent_scope(), ParentScope::Attached(_)),
158
+ "Expected ParentScope::Attached, got {}",
159
+ singleton_name.parent_scope()
160
+ );
161
+
162
+ // The attached parent should be Foo
163
+ let foo_id = singleton_name.parent_scope().expect("Attached should have an id");
164
+ let foo_name = graph.names().get(&foo_id).unwrap();
165
+ assert_eq!(StringId::from("Foo"), *foo_name.str());
166
+ }
167
+
168
+ #[test]
169
+ fn top_level_nesting_is_converted_to_name_id() {
170
+ let mut graph = Graph::new();
171
+
172
+ let (name_id, _) = nesting_stack_to_name_id(&mut graph, "CONST", vec!["Foo".into(), "::Bar".into()]).unwrap();
173
+
174
+ let const_name = graph.names().get(&name_id).unwrap();
175
+ assert_eq!(StringId::from("CONST"), *const_name.str());
176
+ assert!(const_name.parent_scope().is_none());
177
+
178
+ let bar_name = graph.names().get(&const_name.nesting().unwrap()).unwrap();
179
+ assert_eq!(StringId::from("Bar"), *bar_name.str());
180
+ assert!(bar_name.parent_scope().is_top_level());
181
+
182
+ let foo_name = graph.names().get(&bar_name.nesting().unwrap()).unwrap();
183
+ assert_eq!(StringId::from("Foo"), *foo_name.str());
184
+ assert!(foo_name.parent_scope().is_none());
185
+ assert!(foo_name.nesting().is_none());
186
+ }
187
+ }