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,278 +1,279 @@
1
- use std::fmt;
2
-
3
- use crate::model::{
4
- declaration::{Declaration, Namespace},
5
- graph::{BASIC_OBJECT_ID, Graph, OBJECT_ID},
6
- ids::DeclarationId,
7
- };
8
-
9
- #[derive(Debug, PartialEq, Eq)]
10
- pub enum IntegrityErrorKind {
11
- /// A declaration's owner is not a namespace (module, class, or singleton class)
12
- OwnerIsNotNamespace,
13
- /// A declaration's owner does not exist in the graph
14
- OwnerDoesNotExist,
15
- /// A singleton class chain never resolves to a non-singleton namespace
16
- SingletonClassChainDoesNotTerminate,
17
- /// A non-root declaration unexpectedly owns itself
18
- UnexpectedSelfOwnership,
19
- }
20
-
21
- /// An integrity error found during graph validation
22
- #[derive(Debug, PartialEq, Eq)]
23
- pub struct IntegrityError {
24
- kind: IntegrityErrorKind,
25
- declaration_name: String,
26
- uris: Vec<String>,
27
- }
28
-
29
- impl fmt::Display for IntegrityError {
30
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31
- let message = match self.kind {
32
- IntegrityErrorKind::OwnerIsNotNamespace => {
33
- format!("Declaration `{}` is owned by a non-namespace", self.declaration_name)
34
- }
35
- IntegrityErrorKind::OwnerDoesNotExist => {
36
- format!(
37
- "Declaration `{}` has an owner that does not exist in the graph",
38
- self.declaration_name
39
- )
40
- }
41
- IntegrityErrorKind::SingletonClassChainDoesNotTerminate => {
42
- format!(
43
- "Singleton class `{}` does not eventually attach to a non-singleton namespace",
44
- self.declaration_name
45
- )
46
- }
47
- IntegrityErrorKind::UnexpectedSelfOwnership => {
48
- format!("Declaration `{}` unexpectedly owns itself", self.declaration_name)
49
- }
50
- };
51
-
52
- write!(f, "{message}. Defined in: {}", self.uris.join(", "))
53
- }
54
- }
55
-
56
- impl std::error::Error for IntegrityError {}
57
-
58
- /// Checks the integrity of the graph data
59
- #[must_use]
60
- pub fn check_integrity(graph: &Graph) -> Vec<IntegrityError> {
61
- let mut errors = Vec::new();
62
- let self_owners = [*OBJECT_ID, *BASIC_OBJECT_ID];
63
-
64
- for (id, declaration) in graph.declarations() {
65
- let owner_id = declaration.owner_id();
66
-
67
- // Check for constants that own themselves. Only `Object` and `BasicObject` own themselves and no other constant
68
- if *id == *owner_id {
69
- if self_owners.contains(id) {
70
- continue;
71
- }
72
- errors.push(IntegrityError {
73
- kind: IntegrityErrorKind::UnexpectedSelfOwnership,
74
- declaration_name: declaration.name().to_string(),
75
- uris: collect_uris(graph, declaration),
76
- });
77
- continue;
78
- }
79
-
80
- // Check that the owner exists
81
- let Some(owner) = graph.declarations().get(owner_id) else {
82
- errors.push(IntegrityError {
83
- kind: IntegrityErrorKind::OwnerDoesNotExist,
84
- declaration_name: declaration.name().to_string(),
85
- uris: collect_uris(graph, declaration),
86
- });
87
- continue;
88
- };
89
-
90
- // Check that the owner is a namespace
91
- if owner.as_namespace().is_none() {
92
- errors.push(IntegrityError {
93
- kind: IntegrityErrorKind::OwnerIsNotNamespace,
94
- declaration_name: declaration.name().to_string(),
95
- uris: collect_uris(graph, declaration),
96
- });
97
- continue;
98
- }
99
-
100
- // Check singleton class chain termination
101
- if let Declaration::Namespace(Namespace::SingletonClass(_)) = declaration
102
- && !singleton_chain_terminates(graph, *owner_id)
103
- {
104
- errors.push(IntegrityError {
105
- kind: IntegrityErrorKind::SingletonClassChainDoesNotTerminate,
106
- declaration_name: declaration.name().to_string(),
107
- uris: collect_uris(graph, declaration),
108
- });
109
- }
110
- }
111
-
112
- errors
113
- }
114
-
115
- /// Collects the URIs where a declaration is defined, sorted and deduplicated
116
- fn collect_uris(graph: &Graph, declaration: &Declaration) -> Vec<String> {
117
- declaration
118
- .definitions()
119
- .iter()
120
- .map(|def_id| {
121
- let definition = graph.definitions().get(def_id).unwrap();
122
- let document = graph.documents().get(definition.uri_id()).unwrap();
123
- document.uri().to_string()
124
- })
125
- .collect()
126
- }
127
-
128
- /// Walks the singleton class chain to verify that it eventually finds a module or class as its attached object
129
- fn singleton_chain_terminates(graph: &Graph, start_owner_id: DeclarationId) -> bool {
130
- const MAX_SINGLETON_DEPTH: usize = 128;
131
- let mut current_id = start_owner_id;
132
-
133
- for _ in 0..MAX_SINGLETON_DEPTH {
134
- let Some(current) = graph.declarations().get(&current_id) else {
135
- return false;
136
- };
137
-
138
- match current {
139
- Declaration::Namespace(Namespace::SingletonClass(_)) => {
140
- current_id = *current.owner_id();
141
- }
142
- Declaration::Namespace(_) => return true,
143
- _ => return false,
144
- }
145
- }
146
-
147
- false
148
- }
149
-
150
- #[cfg(test)]
151
- mod tests {
152
- use super::*;
153
- use crate::model::declaration::{ClassDeclaration, MethodDeclaration, SingletonClassDeclaration};
154
-
155
- #[test]
156
- fn test_unexpected_self_ownership() {
157
- let mut graph = Graph::new();
158
-
159
- // Object and BasicObject are exempt from self-ownership
160
- graph.declarations_mut().insert(
161
- *OBJECT_ID,
162
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
163
- "Object".to_string(),
164
- *OBJECT_ID,
165
- )))),
166
- );
167
- graph.declarations_mut().insert(
168
- *BASIC_OBJECT_ID,
169
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
170
- "BasicObject".to_string(),
171
- *BASIC_OBJECT_ID,
172
- )))),
173
- );
174
-
175
- // Foo owns itself — should be an error
176
- let foo_id = DeclarationId::from("Foo");
177
- graph.declarations_mut().insert(
178
- foo_id,
179
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
180
- "Foo".to_string(),
181
- foo_id,
182
- )))),
183
- );
184
-
185
- let errors = check_integrity(&graph);
186
- assert_eq!(errors.len(), 1);
187
- assert_eq!(errors[0].kind, IntegrityErrorKind::UnexpectedSelfOwnership);
188
- assert_eq!(errors[0].declaration_name, "Foo");
189
- }
190
-
191
- #[test]
192
- fn test_owner_does_not_exist() {
193
- let mut graph = Graph::new();
194
- let foo_id = DeclarationId::from("Foo");
195
- let bogus_owner = DeclarationId::from("NonExistent");
196
-
197
- graph.declarations_mut().insert(
198
- foo_id,
199
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
200
- "Foo".to_string(),
201
- bogus_owner,
202
- )))),
203
- );
204
-
205
- let errors = check_integrity(&graph);
206
- assert_eq!(errors.len(), 1);
207
- assert_eq!(errors[0].kind, IntegrityErrorKind::OwnerDoesNotExist);
208
- assert_eq!(errors[0].declaration_name, "Foo");
209
- }
210
-
211
- #[test]
212
- fn test_owner_is_not_namespace() {
213
- let mut graph = Graph::new();
214
-
215
- // Object (self-owned, exempt)
216
- graph.declarations_mut().insert(
217
- *OBJECT_ID,
218
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
219
- "Object".to_string(),
220
- *OBJECT_ID,
221
- )))),
222
- );
223
-
224
- // A method owned by Object (valid)
225
- let method_id = DeclarationId::from("Object#foo");
226
- graph.declarations_mut().insert(
227
- method_id,
228
- Declaration::Method(Box::new(MethodDeclaration::new("Object#foo".to_string(), *OBJECT_ID))),
229
- );
230
-
231
- // A class owned by the method (invalid — owner is not a namespace)
232
- let bar_id = DeclarationId::from("Bar");
233
- graph.declarations_mut().insert(
234
- bar_id,
235
- Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
236
- "Bar".to_string(),
237
- method_id,
238
- )))),
239
- );
240
-
241
- let errors = check_integrity(&graph);
242
- assert_eq!(errors.len(), 1);
243
- assert_eq!(errors[0].kind, IntegrityErrorKind::OwnerIsNotNamespace);
244
- assert_eq!(errors[0].declaration_name, "Bar");
245
- }
246
-
247
- #[test]
248
- fn test_singleton_class_chain_does_not_terminate() {
249
- let mut graph = Graph::new();
250
-
251
- // Two singleton classes that own each other, forming a cycle
252
- let s1_id = DeclarationId::from("<Class:Foo>");
253
- let s2_id = DeclarationId::from("<Class:Bar>");
254
-
255
- graph.declarations_mut().insert(
256
- s1_id,
257
- Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
258
- "<Class:Foo>".to_string(),
259
- s2_id,
260
- )))),
261
- );
262
- graph.declarations_mut().insert(
263
- s2_id,
264
- Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
265
- "<Class:Bar>".to_string(),
266
- s1_id,
267
- )))),
268
- );
269
-
270
- let errors = check_integrity(&graph);
271
- assert_eq!(errors.len(), 2);
272
- assert!(
273
- errors
274
- .iter()
275
- .all(|e| e.kind == IntegrityErrorKind::SingletonClassChainDoesNotTerminate)
276
- );
277
- }
278
- }
1
+ use std::fmt;
2
+
3
+ use crate::model::{
4
+ built_in::{BASIC_OBJECT_ID, OBJECT_ID},
5
+ declaration::{Declaration, Namespace},
6
+ graph::Graph,
7
+ ids::DeclarationId,
8
+ };
9
+
10
+ #[derive(Debug, PartialEq, Eq)]
11
+ pub enum IntegrityErrorKind {
12
+ /// A declaration's owner is not a namespace (module, class, or singleton class)
13
+ OwnerIsNotNamespace,
14
+ /// A declaration's owner does not exist in the graph
15
+ OwnerDoesNotExist,
16
+ /// A singleton class chain never resolves to a non-singleton namespace
17
+ SingletonClassChainDoesNotTerminate,
18
+ /// A non-root declaration unexpectedly owns itself
19
+ UnexpectedSelfOwnership,
20
+ }
21
+
22
+ /// An integrity error found during graph validation
23
+ #[derive(Debug, PartialEq, Eq)]
24
+ pub struct IntegrityError {
25
+ kind: IntegrityErrorKind,
26
+ declaration_name: String,
27
+ uris: Vec<String>,
28
+ }
29
+
30
+ impl fmt::Display for IntegrityError {
31
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32
+ let message = match self.kind {
33
+ IntegrityErrorKind::OwnerIsNotNamespace => {
34
+ format!("Declaration `{}` is owned by a non-namespace", self.declaration_name)
35
+ }
36
+ IntegrityErrorKind::OwnerDoesNotExist => {
37
+ format!(
38
+ "Declaration `{}` has an owner that does not exist in the graph",
39
+ self.declaration_name
40
+ )
41
+ }
42
+ IntegrityErrorKind::SingletonClassChainDoesNotTerminate => {
43
+ format!(
44
+ "Singleton class `{}` does not eventually attach to a non-singleton namespace",
45
+ self.declaration_name
46
+ )
47
+ }
48
+ IntegrityErrorKind::UnexpectedSelfOwnership => {
49
+ format!("Declaration `{}` unexpectedly owns itself", self.declaration_name)
50
+ }
51
+ };
52
+
53
+ write!(f, "{message}. Defined in: {}", self.uris.join(", "))
54
+ }
55
+ }
56
+
57
+ impl std::error::Error for IntegrityError {}
58
+
59
+ /// Checks the integrity of the graph data
60
+ #[must_use]
61
+ pub fn check_integrity(graph: &Graph) -> Vec<IntegrityError> {
62
+ let mut errors = Vec::new();
63
+ let self_owners = [*OBJECT_ID, *BASIC_OBJECT_ID];
64
+
65
+ for (id, declaration) in graph.declarations() {
66
+ let owner_id = declaration.owner_id();
67
+
68
+ // Check for constants that own themselves. Only `Object` and `BasicObject` own themselves and no other constant
69
+ if *id == *owner_id {
70
+ if self_owners.contains(id) {
71
+ continue;
72
+ }
73
+ errors.push(IntegrityError {
74
+ kind: IntegrityErrorKind::UnexpectedSelfOwnership,
75
+ declaration_name: declaration.name().to_string(),
76
+ uris: collect_uris(graph, declaration),
77
+ });
78
+ continue;
79
+ }
80
+
81
+ // Check that the owner exists
82
+ let Some(owner) = graph.declarations().get(owner_id) else {
83
+ errors.push(IntegrityError {
84
+ kind: IntegrityErrorKind::OwnerDoesNotExist,
85
+ declaration_name: declaration.name().to_string(),
86
+ uris: collect_uris(graph, declaration),
87
+ });
88
+ continue;
89
+ };
90
+
91
+ // Check that the owner is a namespace
92
+ if owner.as_namespace().is_none() {
93
+ errors.push(IntegrityError {
94
+ kind: IntegrityErrorKind::OwnerIsNotNamespace,
95
+ declaration_name: declaration.name().to_string(),
96
+ uris: collect_uris(graph, declaration),
97
+ });
98
+ continue;
99
+ }
100
+
101
+ // Check singleton class chain termination
102
+ if let Declaration::Namespace(Namespace::SingletonClass(_)) = declaration
103
+ && !singleton_chain_terminates(graph, *owner_id)
104
+ {
105
+ errors.push(IntegrityError {
106
+ kind: IntegrityErrorKind::SingletonClassChainDoesNotTerminate,
107
+ declaration_name: declaration.name().to_string(),
108
+ uris: collect_uris(graph, declaration),
109
+ });
110
+ }
111
+ }
112
+
113
+ errors
114
+ }
115
+
116
+ /// Collects the URIs where a declaration is defined, sorted and deduplicated
117
+ fn collect_uris(graph: &Graph, declaration: &Declaration) -> Vec<String> {
118
+ declaration
119
+ .definitions()
120
+ .iter()
121
+ .map(|def_id| {
122
+ let definition = graph.definitions().get(def_id).unwrap();
123
+ let document = graph.documents().get(definition.uri_id()).unwrap();
124
+ document.uri().to_string()
125
+ })
126
+ .collect()
127
+ }
128
+
129
+ /// Walks the singleton class chain to verify that it eventually finds a module or class as its attached object
130
+ fn singleton_chain_terminates(graph: &Graph, start_owner_id: DeclarationId) -> bool {
131
+ const MAX_SINGLETON_DEPTH: usize = 128;
132
+ let mut current_id = start_owner_id;
133
+
134
+ for _ in 0..MAX_SINGLETON_DEPTH {
135
+ let Some(current) = graph.declarations().get(&current_id) else {
136
+ return false;
137
+ };
138
+
139
+ match current {
140
+ Declaration::Namespace(Namespace::SingletonClass(_)) => {
141
+ current_id = *current.owner_id();
142
+ }
143
+ Declaration::Namespace(_) => return true,
144
+ _ => return false,
145
+ }
146
+ }
147
+
148
+ false
149
+ }
150
+
151
+ #[cfg(test)]
152
+ mod tests {
153
+ use super::*;
154
+ use crate::model::declaration::{ClassDeclaration, MethodDeclaration, SingletonClassDeclaration};
155
+
156
+ #[test]
157
+ fn test_unexpected_self_ownership() {
158
+ let mut graph = Graph::new();
159
+
160
+ // Object and BasicObject are exempt from self-ownership
161
+ graph.declarations_mut().insert(
162
+ *OBJECT_ID,
163
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
164
+ "Object".to_string(),
165
+ *OBJECT_ID,
166
+ )))),
167
+ );
168
+ graph.declarations_mut().insert(
169
+ *BASIC_OBJECT_ID,
170
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
171
+ "BasicObject".to_string(),
172
+ *BASIC_OBJECT_ID,
173
+ )))),
174
+ );
175
+
176
+ // Foo owns itself — should be an error
177
+ let foo_id = DeclarationId::from("Foo");
178
+ graph.declarations_mut().insert(
179
+ foo_id,
180
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
181
+ "Foo".to_string(),
182
+ foo_id,
183
+ )))),
184
+ );
185
+
186
+ let errors = check_integrity(&graph);
187
+ assert_eq!(errors.len(), 1);
188
+ assert_eq!(errors[0].kind, IntegrityErrorKind::UnexpectedSelfOwnership);
189
+ assert_eq!(errors[0].declaration_name, "Foo");
190
+ }
191
+
192
+ #[test]
193
+ fn test_owner_does_not_exist() {
194
+ let mut graph = Graph::new();
195
+ let foo_id = DeclarationId::from("Foo");
196
+ let bogus_owner = DeclarationId::from("NonExistent");
197
+
198
+ graph.declarations_mut().insert(
199
+ foo_id,
200
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
201
+ "Foo".to_string(),
202
+ bogus_owner,
203
+ )))),
204
+ );
205
+
206
+ let errors = check_integrity(&graph);
207
+ assert_eq!(errors.len(), 1);
208
+ assert_eq!(errors[0].kind, IntegrityErrorKind::OwnerDoesNotExist);
209
+ assert_eq!(errors[0].declaration_name, "Foo");
210
+ }
211
+
212
+ #[test]
213
+ fn test_owner_is_not_namespace() {
214
+ let mut graph = Graph::new();
215
+
216
+ // Object (self-owned, exempt)
217
+ graph.declarations_mut().insert(
218
+ *OBJECT_ID,
219
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
220
+ "Object".to_string(),
221
+ *OBJECT_ID,
222
+ )))),
223
+ );
224
+
225
+ // A method owned by Object (valid)
226
+ let method_id = DeclarationId::from("Object#foo");
227
+ graph.declarations_mut().insert(
228
+ method_id,
229
+ Declaration::Method(Box::new(MethodDeclaration::new("Object#foo".to_string(), *OBJECT_ID))),
230
+ );
231
+
232
+ // A class owned by the method (invalid — owner is not a namespace)
233
+ let bar_id = DeclarationId::from("Bar");
234
+ graph.declarations_mut().insert(
235
+ bar_id,
236
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
237
+ "Bar".to_string(),
238
+ method_id,
239
+ )))),
240
+ );
241
+
242
+ let errors = check_integrity(&graph);
243
+ assert_eq!(errors.len(), 1);
244
+ assert_eq!(errors[0].kind, IntegrityErrorKind::OwnerIsNotNamespace);
245
+ assert_eq!(errors[0].declaration_name, "Bar");
246
+ }
247
+
248
+ #[test]
249
+ fn test_singleton_class_chain_does_not_terminate() {
250
+ let mut graph = Graph::new();
251
+
252
+ // Two singleton classes that own each other, forming a cycle
253
+ let s1_id = DeclarationId::from("<Class:Foo>");
254
+ let s2_id = DeclarationId::from("<Class:Bar>");
255
+
256
+ graph.declarations_mut().insert(
257
+ s1_id,
258
+ Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
259
+ "<Class:Foo>".to_string(),
260
+ s2_id,
261
+ )))),
262
+ );
263
+ graph.declarations_mut().insert(
264
+ s2_id,
265
+ Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
266
+ "<Class:Bar>".to_string(),
267
+ s1_id,
268
+ )))),
269
+ );
270
+
271
+ let errors = check_integrity(&graph);
272
+ assert_eq!(errors.len(), 2);
273
+ assert!(
274
+ errors
275
+ .iter()
276
+ .all(|e| e.kind == IntegrityErrorKind::SingletonClassChainDoesNotTerminate)
277
+ );
278
+ }
279
+ }