rubydex 0.1.0.beta1-x86_64-linux → 0.1.0.beta2-x86_64-linux

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/ext/rubydex/declaration.c +146 -0
  3. data/ext/rubydex/declaration.h +10 -0
  4. data/ext/rubydex/definition.c +234 -0
  5. data/ext/rubydex/definition.h +28 -0
  6. data/ext/rubydex/diagnostic.c +6 -0
  7. data/ext/rubydex/diagnostic.h +11 -0
  8. data/ext/rubydex/document.c +98 -0
  9. data/ext/rubydex/document.h +10 -0
  10. data/ext/rubydex/extconf.rb +36 -15
  11. data/ext/rubydex/graph.c +405 -0
  12. data/ext/rubydex/graph.h +10 -0
  13. data/ext/rubydex/handle.h +44 -0
  14. data/ext/rubydex/location.c +22 -0
  15. data/ext/rubydex/location.h +15 -0
  16. data/ext/rubydex/reference.c +104 -0
  17. data/ext/rubydex/reference.h +16 -0
  18. data/ext/rubydex/rubydex.c +22 -0
  19. data/ext/rubydex/utils.c +27 -0
  20. data/ext/rubydex/utils.h +13 -0
  21. data/lib/rubydex/3.2/rubydex.so +0 -0
  22. data/lib/rubydex/3.3/rubydex.so +0 -0
  23. data/lib/rubydex/3.4/rubydex.so +0 -0
  24. data/lib/rubydex/4.0/rubydex.so +0 -0
  25. data/lib/rubydex/librubydex_sys.so +0 -0
  26. data/lib/rubydex/version.rb +1 -1
  27. data/rust/Cargo.lock +1275 -0
  28. data/rust/Cargo.toml +23 -0
  29. data/rust/about.hbs +78 -0
  30. data/rust/about.toml +9 -0
  31. data/rust/rubydex/Cargo.toml +41 -0
  32. data/rust/rubydex/src/diagnostic.rs +108 -0
  33. data/rust/rubydex/src/errors.rs +28 -0
  34. data/rust/rubydex/src/indexing/local_graph.rs +172 -0
  35. data/rust/rubydex/src/indexing/ruby_indexer.rs +5397 -0
  36. data/rust/rubydex/src/indexing.rs +128 -0
  37. data/rust/rubydex/src/job_queue.rs +186 -0
  38. data/rust/rubydex/src/lib.rs +15 -0
  39. data/rust/rubydex/src/listing.rs +249 -0
  40. data/rust/rubydex/src/main.rs +116 -0
  41. data/rust/rubydex/src/model/comment.rs +24 -0
  42. data/rust/rubydex/src/model/declaration.rs +541 -0
  43. data/rust/rubydex/src/model/definitions.rs +1475 -0
  44. data/rust/rubydex/src/model/document.rs +111 -0
  45. data/rust/rubydex/src/model/encoding.rs +22 -0
  46. data/rust/rubydex/src/model/graph.rs +1387 -0
  47. data/rust/rubydex/src/model/id.rs +90 -0
  48. data/rust/rubydex/src/model/identity_maps.rs +54 -0
  49. data/rust/rubydex/src/model/ids.rs +32 -0
  50. data/rust/rubydex/src/model/name.rs +188 -0
  51. data/rust/rubydex/src/model/references.rs +129 -0
  52. data/rust/rubydex/src/model/string_ref.rs +44 -0
  53. data/rust/rubydex/src/model/visibility.rs +41 -0
  54. data/rust/rubydex/src/model.rs +13 -0
  55. data/rust/rubydex/src/offset.rs +70 -0
  56. data/rust/rubydex/src/position.rs +6 -0
  57. data/rust/rubydex/src/query.rs +103 -0
  58. data/rust/rubydex/src/resolution.rs +4421 -0
  59. data/rust/rubydex/src/stats/memory.rs +71 -0
  60. data/rust/rubydex/src/stats/timer.rs +126 -0
  61. data/rust/rubydex/src/stats.rs +9 -0
  62. data/rust/rubydex/src/test_utils/context.rs +226 -0
  63. data/rust/rubydex/src/test_utils/graph_test.rs +229 -0
  64. data/rust/rubydex/src/test_utils/local_graph_test.rs +166 -0
  65. data/rust/rubydex/src/test_utils.rs +52 -0
  66. data/rust/rubydex/src/visualization/dot.rs +176 -0
  67. data/rust/rubydex/src/visualization.rs +6 -0
  68. data/rust/rubydex/tests/cli.rs +167 -0
  69. data/rust/rubydex-sys/Cargo.toml +20 -0
  70. data/rust/rubydex-sys/build.rs +14 -0
  71. data/rust/rubydex-sys/cbindgen.toml +12 -0
  72. data/rust/rubydex-sys/src/declaration_api.rs +114 -0
  73. data/rust/rubydex-sys/src/definition_api.rs +350 -0
  74. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  75. data/rust/rubydex-sys/src/document_api.rs +54 -0
  76. data/rust/rubydex-sys/src/graph_api.rs +493 -0
  77. data/rust/rubydex-sys/src/lib.rs +9 -0
  78. data/rust/rubydex-sys/src/location_api.rs +79 -0
  79. data/rust/rubydex-sys/src/name_api.rs +81 -0
  80. data/rust/rubydex-sys/src/reference_api.rs +191 -0
  81. data/rust/rubydex-sys/src/utils.rs +50 -0
  82. data/rust/rustfmt.toml +2 -0
  83. metadata +77 -2
@@ -0,0 +1,4421 @@
1
+ use std::{
2
+ collections::{HashSet, VecDeque},
3
+ hash::BuildHasher,
4
+ };
5
+
6
+ use crate::model::{
7
+ declaration::{
8
+ Ancestor, Ancestors, ClassDeclaration, ClassVariableDeclaration, ConstantAliasDeclaration, ConstantDeclaration,
9
+ Declaration, GlobalVariableDeclaration, InstanceVariableDeclaration, MethodDeclaration, ModuleDeclaration,
10
+ Namespace, SingletonClassDeclaration,
11
+ },
12
+ definitions::{Definition, Mixin},
13
+ graph::{CLASS_ID, Graph, MODULE_ID, OBJECT_ID},
14
+ identity_maps::{IdentityHashMap, IdentityHashSet},
15
+ ids::{DeclarationId, DefinitionId, NameId, ReferenceId, StringId},
16
+ name::{Name, NameRef},
17
+ };
18
+
19
+ pub enum Unit {
20
+ /// A definition that defines a constant and might require resolution
21
+ Definition(DefinitionId),
22
+ /// A constant reference that needs to be resolved
23
+ Reference(ReferenceId),
24
+ /// A list of ancestors that have been partially linearized and need to be retried
25
+ Ancestors(DeclarationId),
26
+ }
27
+
28
+ enum Outcome {
29
+ /// The constant was successfully resolved to the given declaration ID. The second optional tuple element is a
30
+ /// declaration that still needs to have its ancestors linearized
31
+ Resolved(DeclarationId, Option<DeclarationId>),
32
+ /// We had everything we needed to resolved this constant, but we couldn't find it. This means it's not defined (or
33
+ /// defined in a way that static analysis won't discover it). Failing to resolve a constant may also uncovered
34
+ /// ancestors that require linearization, which is the second element
35
+ Unresolved(Option<DeclarationId>),
36
+ /// We couldn't resolve this constant right now because certain dependencies were missing. For example, a constant
37
+ /// reference involved in computing ancestors (like an include) was found, but wasn't resolved yet. We need to place
38
+ /// this back in the queue to retry once we have progressed further
39
+ Retry,
40
+ }
41
+
42
+ impl Outcome {
43
+ fn is_resolved_or_retry(&self) -> bool {
44
+ matches!(self, Outcome::Resolved(_, _) | Outcome::Retry)
45
+ }
46
+ }
47
+
48
+ struct LinearizationContext {
49
+ descendants: IdentityHashSet<DeclarationId>,
50
+ seen_ids: IdentityHashSet<DeclarationId>,
51
+ cyclic: bool,
52
+ partial: bool,
53
+ }
54
+
55
+ impl LinearizationContext {
56
+ fn new() -> Self {
57
+ Self {
58
+ descendants: IdentityHashSet::default(),
59
+ seen_ids: IdentityHashSet::default(),
60
+ cyclic: false,
61
+ partial: false,
62
+ }
63
+ }
64
+ }
65
+
66
+ pub struct Resolver<'a> {
67
+ graph: &'a mut Graph,
68
+ }
69
+
70
+ impl<'a> Resolver<'a> {
71
+ pub fn new(graph: &'a mut Graph) -> Self {
72
+ Self { graph }
73
+ }
74
+
75
+ /// Runs the resolution phase on the graph. The resolution phase is when 4 main pieces of information are computed:
76
+ ///
77
+ /// 1. Declarations for all definitions
78
+ /// 2. Members and ownership for all declarations
79
+ /// 3. Resolution of all constant references
80
+ /// 4. Inheritance relationships between declarations
81
+ ///
82
+ /// # Panics
83
+ ///
84
+ /// Can panic if there's inconsistent data in the graph
85
+ pub fn resolve_all(&mut self) {
86
+ // TODO: temporary code while we don't have synchronization. We clear all declarations instead of doing the minimal
87
+ // amount of work
88
+ self.graph.clear_declarations();
89
+ // Ensure that Object exists ahead of time so that we can associate top level declarations with the right membership
90
+
91
+ {
92
+ self.graph.declarations_mut().insert(
93
+ *OBJECT_ID,
94
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
95
+ "Object".to_string(),
96
+ *OBJECT_ID,
97
+ )))),
98
+ );
99
+ self.graph.declarations_mut().insert(
100
+ *MODULE_ID,
101
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
102
+ "Module".to_string(),
103
+ *OBJECT_ID,
104
+ )))),
105
+ );
106
+ self.graph.declarations_mut().insert(
107
+ *CLASS_ID,
108
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(
109
+ "Class".to_string(),
110
+ *OBJECT_ID,
111
+ )))),
112
+ );
113
+ }
114
+
115
+ let (mut unit_queue, other_ids) = self.sorted_units();
116
+
117
+ loop {
118
+ // Flag to ensure the end of the resolution loop. We go through all items in the queue based on its current
119
+ // length. If we made any progress in this pass of the queue, we can continue because we're unlocking more work
120
+ // to be done
121
+ let mut made_progress = false;
122
+
123
+ // Loop through the current length of the queue, which won't change during this pass. Retries pushed to the back
124
+ // are only processed in the next pass, so that we can assess whether we made any progress
125
+ for _ in 0..unit_queue.len() {
126
+ let Some(unit_id) = unit_queue.pop_front() else {
127
+ break;
128
+ };
129
+
130
+ match unit_id {
131
+ Unit::Definition(id) => {
132
+ self.handle_definition_unit(&mut unit_queue, &mut made_progress, unit_id, id);
133
+ }
134
+ Unit::Reference(id) => {
135
+ self.handle_reference_unit(&mut unit_queue, &mut made_progress, unit_id, id);
136
+ }
137
+ Unit::Ancestors(id) => {
138
+ self.handle_ancestor_unit(&mut unit_queue, &mut made_progress, id);
139
+ }
140
+ }
141
+ }
142
+
143
+ if !made_progress || unit_queue.is_empty() {
144
+ break;
145
+ }
146
+ }
147
+
148
+ self.handle_remaining_definitions(other_ids);
149
+ }
150
+
151
+ /// Resolves a single constant against the graph. This method is not meant to be used by the resolution phase, but by
152
+ /// the Ruby API
153
+ pub fn resolve_constant(&mut self, name_id: NameId) -> Option<DeclarationId> {
154
+ match self.resolve_constant_internal(name_id) {
155
+ Outcome::Resolved(id, _) => Some(id),
156
+ Outcome::Unresolved(_) | Outcome::Retry => None,
157
+ }
158
+ }
159
+
160
+ /// Handles a unit of work for resolving a constant definition
161
+ fn handle_definition_unit(
162
+ &mut self,
163
+ unit_queue: &mut VecDeque<Unit>,
164
+ made_progress: &mut bool,
165
+ unit_id: Unit,
166
+ id: DefinitionId,
167
+ ) {
168
+ let outcome = match self.graph.definitions().get(&id).unwrap() {
169
+ Definition::Class(class) => {
170
+ self.handle_constant_declaration(*class.name_id(), id, false, |name, owner_id| {
171
+ Declaration::Namespace(Namespace::Class(Box::new(ClassDeclaration::new(name, owner_id))))
172
+ })
173
+ }
174
+ Definition::Module(module) => {
175
+ self.handle_constant_declaration(*module.name_id(), id, false, |name, owner_id| {
176
+ Declaration::Namespace(Namespace::Module(Box::new(ModuleDeclaration::new(name, owner_id))))
177
+ })
178
+ }
179
+ Definition::Constant(constant) => {
180
+ self.handle_constant_declaration(*constant.name_id(), id, false, |name, owner_id| {
181
+ Declaration::Constant(Box::new(ConstantDeclaration::new(name, owner_id)))
182
+ })
183
+ }
184
+ Definition::ConstantAlias(alias) => {
185
+ self.handle_constant_declaration(*alias.name_id(), id, false, |name, owner_id| {
186
+ Declaration::ConstantAlias(Box::new(ConstantAliasDeclaration::new(name, owner_id)))
187
+ })
188
+ }
189
+ Definition::SingletonClass(singleton) => {
190
+ self.handle_constant_declaration(*singleton.name_id(), id, true, |name, owner_id| {
191
+ Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
192
+ name, owner_id,
193
+ ))))
194
+ })
195
+ }
196
+ _ => panic!("Expected constant definitions"),
197
+ };
198
+
199
+ match outcome {
200
+ Outcome::Retry => {
201
+ // There might be dependencies we haven't figured out yet, so we need to retry
202
+ unit_queue.push_back(unit_id);
203
+ }
204
+ Outcome::Unresolved(None) => {
205
+ // We couldn't resolve this name. Emit a diagnostic
206
+ }
207
+ Outcome::Unresolved(Some(id_needing_linearization)) => {
208
+ unit_queue.push_back(unit_id);
209
+ unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
210
+ }
211
+ Outcome::Resolved(id, None) => {
212
+ unit_queue.push_back(Unit::Ancestors(id));
213
+ *made_progress = true;
214
+ }
215
+ Outcome::Resolved(_, Some(id_needing_linearization)) => {
216
+ unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
217
+ *made_progress = true;
218
+ }
219
+ }
220
+ }
221
+
222
+ /// Handles a unit of work for resolving a constant reference
223
+ fn handle_reference_unit(
224
+ &mut self,
225
+ unit_queue: &mut VecDeque<Unit>,
226
+ made_progress: &mut bool,
227
+ unit_id: Unit,
228
+ id: ReferenceId,
229
+ ) {
230
+ let constant_ref = self.graph.constant_references().get(&id).unwrap();
231
+
232
+ match self.resolve_constant_internal(*constant_ref.name_id()) {
233
+ Outcome::Retry => {
234
+ // There might be dependencies we haven't figured out yet, so we need to retry
235
+ unit_queue.push_back(unit_id);
236
+ }
237
+ Outcome::Unresolved(None) => {
238
+ // We couldn't resolve this name. Emit a diagnostic
239
+ }
240
+ Outcome::Unresolved(Some(id_needing_linearization)) => {
241
+ unit_queue.push_back(unit_id);
242
+ unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
243
+ }
244
+ Outcome::Resolved(declaration_id, None) => {
245
+ self.graph.record_resolved_reference(id, declaration_id);
246
+ *made_progress = true;
247
+ }
248
+ Outcome::Resolved(resolved_id, Some(id_needing_linearization)) => {
249
+ self.graph.record_resolved_reference(id, resolved_id);
250
+ *made_progress = true;
251
+ unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
252
+ }
253
+ }
254
+ }
255
+
256
+ /// Handles a unit of work for linearizing ancestors of a declaration
257
+ fn handle_ancestor_unit(&mut self, unit_queue: &mut VecDeque<Unit>, made_progress: &mut bool, id: DeclarationId) {
258
+ match self.ancestors_of(id) {
259
+ Ancestors::Complete(_) | Ancestors::Cyclic(_) => {
260
+ // We succeeded in some capacity this time
261
+ *made_progress = true;
262
+ }
263
+ Ancestors::Partial(_) => {
264
+ // We still couldn't linearize ancestors, but there's a chance that this will succeed next time. We
265
+ // re-enqueue for another try, but we don't consider it as making progress
266
+ unit_queue.push_back(Unit::Ancestors(id));
267
+ }
268
+ }
269
+ }
270
+
271
+ /// Handle other definitions that don't require resolution, but need to have their declarations and membership created
272
+ #[allow(clippy::too_many_lines)]
273
+ fn handle_remaining_definitions(
274
+ &mut self,
275
+ other_ids: Vec<crate::model::id::Id<crate::model::ids::DefinitionMarker>>,
276
+ ) {
277
+ for id in other_ids {
278
+ match self.graph.definitions().get(&id).unwrap() {
279
+ Definition::Method(method_definition) => {
280
+ let str_id = *method_definition.str_id();
281
+ let owner_id = if let Some(receiver) = method_definition.receiver() {
282
+ let receiver_decl_id = match self.graph.names().get(receiver).unwrap() {
283
+ NameRef::Resolved(resolved) => *resolved.declaration_id(),
284
+ NameRef::Unresolved(_) => {
285
+ // Error diagnostic: if we couldn't resolve the constant being used, then we don't know
286
+ // where this method is being defined. For example:
287
+ //
288
+ // def Foo.bar; end
289
+ //
290
+ // where Foo is undefined
291
+ continue;
292
+ }
293
+ };
294
+
295
+ self.get_or_create_singleton_class(receiver_decl_id)
296
+ } else {
297
+ self.resolve_lexical_owner(*method_definition.lexical_nesting_id())
298
+ };
299
+
300
+ self.create_declaration(str_id, id, owner_id, |name| {
301
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
302
+ });
303
+ }
304
+ Definition::AttrAccessor(attr) => {
305
+ let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
306
+
307
+ self.create_declaration(*attr.str_id(), id, owner_id, |name| {
308
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
309
+ });
310
+ }
311
+ Definition::AttrReader(attr) => {
312
+ let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
313
+
314
+ self.create_declaration(*attr.str_id(), id, owner_id, |name| {
315
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
316
+ });
317
+ }
318
+ Definition::AttrWriter(attr) => {
319
+ let owner_id = self.resolve_lexical_owner(*attr.lexical_nesting_id());
320
+
321
+ self.create_declaration(*attr.str_id(), id, owner_id, |name| {
322
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
323
+ });
324
+ }
325
+ Definition::GlobalVariable(var) => {
326
+ let owner_id = *OBJECT_ID;
327
+ let str_id = *var.str_id();
328
+ let name = self.graph.strings().get(&str_id).unwrap().as_str().to_string();
329
+ let declaration_id = DeclarationId::from(&name);
330
+
331
+ self.graph.add_declaration(declaration_id, id, || {
332
+ Declaration::GlobalVariable(Box::new(GlobalVariableDeclaration::new(name, owner_id)))
333
+ });
334
+ self.graph.add_member(&owner_id, declaration_id, str_id);
335
+ }
336
+ Definition::InstanceVariable(var) => {
337
+ let str_id = *var.str_id();
338
+
339
+ // Top-level instance variables belong to the `<main>` object, not `Object`.
340
+ // We can't represent `<main>` yet, so skip creating declarations for these.
341
+ // TODO: Make sure we introduce `<main>` representation later and update this
342
+ let Some(nesting_id) = *var.lexical_nesting_id() else {
343
+ continue;
344
+ };
345
+
346
+ let Some(nesting_def) = self.graph.definitions().get(&nesting_id) else {
347
+ continue;
348
+ };
349
+
350
+ match nesting_def {
351
+ // When the instance variable is inside a method body, we determine the owner based on the method's receiver
352
+ Definition::Method(method) => {
353
+ // Method has explicit receiver (def self.foo or def Foo.bar)
354
+ if let Some(receiver_name_id) = method.receiver() {
355
+ let Some(NameRef::Resolved(resolved)) = self.graph.names().get(receiver_name_id) else {
356
+ // TODO: add diagnostic for unresolved receiver
357
+ continue;
358
+ };
359
+ let receiver_decl_id = *resolved.declaration_id();
360
+
361
+ // Instance variable in singleton method - owned by the receiver's singleton class
362
+ let owner_id = self.get_or_create_singleton_class(receiver_decl_id);
363
+ {
364
+ debug_assert!(
365
+ matches!(
366
+ self.graph.declarations().get(&owner_id),
367
+ Some(Declaration::Namespace(Namespace::SingletonClass(_)))
368
+ ),
369
+ "Instance variable in singleton method should be owned by a SingletonClass"
370
+ );
371
+ }
372
+ self.create_declaration(str_id, id, owner_id, |name| {
373
+ Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(
374
+ name, owner_id,
375
+ )))
376
+ });
377
+ continue;
378
+ }
379
+
380
+ // If the method has no explicit receiver, we resolve the owner based on the lexical nesting
381
+ let method_owner_id = self.resolve_lexical_owner(*method.lexical_nesting_id());
382
+
383
+ // If the method is in a singleton class, the instance variable belongs to the class object
384
+ // Like `class << Foo; def bar; @bar = 1; end; end`, where `@bar` is owned by `Foo::<Foo>`
385
+ if let Some(decl) = self.graph.declarations().get(&method_owner_id)
386
+ && matches!(decl, Declaration::Namespace(Namespace::SingletonClass(_)))
387
+ {
388
+ // Method in singleton class - owner is the singleton class itself
389
+ self.create_declaration(str_id, id, method_owner_id, |name| {
390
+ Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(
391
+ name,
392
+ method_owner_id,
393
+ )))
394
+ });
395
+ } else {
396
+ // Regular instance method
397
+ // Create an instance variable declaration for the method's owner
398
+ self.create_declaration(str_id, id, method_owner_id, |name| {
399
+ Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(
400
+ name,
401
+ method_owner_id,
402
+ )))
403
+ });
404
+ }
405
+ }
406
+ // If the instance variable is directly in a class/module body, it belongs to the class object
407
+ // and is owned by the singleton class of that class/module
408
+ Definition::Class(_) | Definition::Module(_) => {
409
+ let nesting_decl_id = self
410
+ .graph
411
+ .definitions_to_declarations()
412
+ .get(&nesting_id)
413
+ .copied()
414
+ .unwrap_or(*OBJECT_ID);
415
+ let owner_id = self.get_or_create_singleton_class(nesting_decl_id);
416
+ {
417
+ debug_assert!(
418
+ matches!(
419
+ self.graph.declarations().get(&owner_id),
420
+ Some(Declaration::Namespace(Namespace::SingletonClass(_)))
421
+ ),
422
+ "Instance variable in class/module body should be owned by a SingletonClass"
423
+ );
424
+ }
425
+ self.create_declaration(str_id, id, owner_id, |name| {
426
+ Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(
427
+ name, owner_id,
428
+ )))
429
+ });
430
+ }
431
+ // If in a singleton class body directly, the owner is the singleton class's singleton class
432
+ // Like `class << Foo; @bar = 1; end`, where `@bar` is owned by `Foo::<Foo>::<<Foo>>`
433
+ Definition::SingletonClass(_) => {
434
+ let singleton_class_decl_id = self
435
+ .graph
436
+ .definitions_to_declarations()
437
+ .get(&nesting_id)
438
+ .copied()
439
+ .unwrap_or(*OBJECT_ID);
440
+ let owner_id = self.get_or_create_singleton_class(singleton_class_decl_id);
441
+ {
442
+ debug_assert!(
443
+ matches!(
444
+ self.graph.declarations().get(&owner_id),
445
+ Some(Declaration::Namespace(Namespace::SingletonClass(_)))
446
+ ),
447
+ "Instance variable in singleton class body should be owned by a SingletonClass"
448
+ );
449
+ }
450
+ self.create_declaration(str_id, id, owner_id, |name| {
451
+ Declaration::InstanceVariable(Box::new(InstanceVariableDeclaration::new(
452
+ name, owner_id,
453
+ )))
454
+ });
455
+ }
456
+ _ => {
457
+ panic!("Unexpected lexical nesting for instance variable: {nesting_def:?}");
458
+ }
459
+ }
460
+ }
461
+ Definition::ClassVariable(var) => {
462
+ // TODO: add diagnostic on the else branch. Defining class variables at the top level crashes
463
+ if let Some(owner_id) = self.resolve_class_variable_owner(*var.lexical_nesting_id()) {
464
+ self.create_declaration(*var.str_id(), id, owner_id, |name| {
465
+ Declaration::ClassVariable(Box::new(ClassVariableDeclaration::new(name, owner_id)))
466
+ });
467
+ }
468
+ }
469
+ Definition::MethodAlias(alias) => {
470
+ let owner_id = self.resolve_lexical_owner(*alias.lexical_nesting_id());
471
+
472
+ self.create_declaration(*alias.new_name_str_id(), id, owner_id, |name| {
473
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
474
+ });
475
+ }
476
+ Definition::GlobalVariableAlias(alias) => {
477
+ self.create_declaration(*alias.new_name_str_id(), id, *OBJECT_ID, |name| {
478
+ Declaration::GlobalVariable(Box::new(GlobalVariableDeclaration::new(name, *OBJECT_ID)))
479
+ });
480
+ }
481
+ Definition::Class(_)
482
+ | Definition::SingletonClass(_)
483
+ | Definition::Module(_)
484
+ | Definition::Constant(_)
485
+ | Definition::ConstantAlias(_) => {
486
+ panic!("Unexpected definition type in non-constant resolution. This shouldn't happen")
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ fn create_declaration<F>(
493
+ &mut self,
494
+ str_id: StringId,
495
+ definition_id: DefinitionId,
496
+ owner_id: DeclarationId,
497
+ declaration_builder: F,
498
+ ) where
499
+ F: FnOnce(String) -> Declaration,
500
+ {
501
+ let fully_qualified_name = {
502
+ let owner = self.graph.declarations().get(&owner_id).unwrap();
503
+ let name_str = self.graph.strings().get(&str_id).unwrap();
504
+ format!("{}#{}", owner.name(), name_str.as_str())
505
+ };
506
+ let declaration_id = DeclarationId::from(&fully_qualified_name);
507
+
508
+ self.graph.add_declaration(declaration_id, definition_id, || {
509
+ declaration_builder(fully_qualified_name)
510
+ });
511
+ self.graph.add_member(&owner_id, declaration_id, str_id);
512
+ }
513
+
514
+ /// Resolves owner for class variables, bypassing singleton classes.
515
+ fn resolve_class_variable_owner(&self, lexical_nesting_id: Option<DefinitionId>) -> Option<DeclarationId> {
516
+ let mut current_nesting = lexical_nesting_id;
517
+ while let Some(nesting_id) = current_nesting {
518
+ if let Some(nesting_def) = self.graph.definitions().get(&nesting_id)
519
+ && matches!(nesting_def, Definition::SingletonClass(_))
520
+ {
521
+ current_nesting = *nesting_def.lexical_nesting_id();
522
+ } else {
523
+ break;
524
+ }
525
+ }
526
+ current_nesting.and_then(|id| self.graph.definitions_to_declarations().get(&id).copied())
527
+ }
528
+
529
+ /// Resolves owner from lexical nesting.
530
+ fn resolve_lexical_owner(&self, lexical_nesting_id: Option<DefinitionId>) -> DeclarationId {
531
+ let Some(id) = lexical_nesting_id else {
532
+ return *OBJECT_ID;
533
+ };
534
+
535
+ // If no declaration exists yet for this definition, walk up the lexical chain.
536
+ // This handles the case where attr_* definitions inside methods are processed
537
+ // before the method definition itself.
538
+ let Some(declaration_id) = self.graph.definitions_to_declarations().get(&id) else {
539
+ let definition = self.graph.definitions().get(&id).unwrap();
540
+ return self.resolve_lexical_owner(*definition.lexical_nesting_id());
541
+ };
542
+
543
+ let declarations = self.graph.declarations();
544
+
545
+ // If the associated declaration is a namespace that can own things, we found the right owner. Otherwise, we might
546
+ // have found something nested inside something else (like a method), in which case we have to recurse until we find
547
+ // the appropriate owner
548
+ if matches!(
549
+ declarations.get(declaration_id).unwrap(),
550
+ Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_) | Namespace::SingletonClass(_))
551
+ ) {
552
+ *declaration_id
553
+ } else {
554
+ let definition = self.graph.definitions().get(&id).unwrap();
555
+ self.resolve_lexical_owner(*definition.lexical_nesting_id())
556
+ }
557
+ }
558
+
559
+ /// Gets or creates a singleton class declaration for a given class/module declaration.
560
+ /// For class `Foo`, this returns the declaration for `Foo::<Foo>`.
561
+ fn get_or_create_singleton_class(&mut self, attached_id: DeclarationId) -> DeclarationId {
562
+ let (decl_id, name) = {
563
+ let attached_decl = self.graph.declarations().get(&attached_id).unwrap();
564
+
565
+ // TODO: the constant check is a temporary hack. We need to implement proper handling for `Struct.new`, `Class.new`
566
+ // and `Module.new`, which now seem like constants, but are actually namespaces
567
+ if !matches!(attached_decl, Declaration::Constant(_) | Declaration::ConstantAlias(_))
568
+ && let Some(singleton_id) = attached_decl.as_namespace().unwrap().singleton_class()
569
+ {
570
+ return *singleton_id;
571
+ }
572
+
573
+ let name = format!("{}::<{}>", attached_decl.name(), attached_decl.unqualified_name());
574
+ (DeclarationId::from(&name), name)
575
+ };
576
+
577
+ // TODO: the constant check is a temporary hack. We need to implement proper handling for `Struct.new`, `Class.new`
578
+ // and `Module.new`, which now seem like constants, but are actually namespaces
579
+ if !matches!(
580
+ self.graph.declarations().get(&attached_id).unwrap(),
581
+ Declaration::Constant(_) | Declaration::ConstantAlias(_)
582
+ ) {
583
+ self.graph
584
+ .declarations_mut()
585
+ .get_mut(&attached_id)
586
+ .unwrap()
587
+ .as_namespace_mut()
588
+ .unwrap()
589
+ .set_singleton_class_id(decl_id);
590
+ }
591
+
592
+ self.graph.declarations_mut().insert(
593
+ decl_id,
594
+ Declaration::Namespace(Namespace::SingletonClass(Box::new(SingletonClassDeclaration::new(
595
+ name,
596
+ attached_id,
597
+ )))),
598
+ );
599
+
600
+ decl_id
601
+ }
602
+
603
+ /// Linearizes the ancestors of a declaration, returning the list of ancestor declaration IDs
604
+ ///
605
+ /// # Panics
606
+ ///
607
+ /// Can panic if there's inconsistent data in the graph
608
+ #[must_use]
609
+ fn ancestors_of(&mut self, declaration_id: DeclarationId) -> Ancestors {
610
+ let mut context = LinearizationContext::new();
611
+ self.linearize_ancestors(declaration_id, &mut context)
612
+ }
613
+
614
+ /// Linearizes the ancestors of a declaration, returning the list of ancestor declaration IDs
615
+ ///
616
+ /// # Panics
617
+ ///
618
+ /// Can panic if there's inconsistent data in the graph
619
+ #[must_use]
620
+ fn linearize_ancestors(&mut self, declaration_id: DeclarationId, context: &mut LinearizationContext) -> Ancestors {
621
+ {
622
+ let declaration = self.graph.declarations_mut().get_mut(&declaration_id).unwrap();
623
+
624
+ // TODO: this is a temporary hack. We need to implement proper handling for `Struct.new`, `Class.new` and
625
+ // `Module.new`, which now seem like constants, but are actually namespaces
626
+ if matches!(declaration, Declaration::Constant(_) | Declaration::ConstantAlias(_)) {
627
+ return Ancestors::Complete(vec![]);
628
+ }
629
+
630
+ // Add this declaration to the descendants so that we capture transitive descendant relationships
631
+ context.descendants.insert(declaration_id);
632
+
633
+ // Return the cached ancestors if we already computed them. If they are partial ancestors, ignore the cache to try
634
+ // again
635
+ if declaration.as_namespace().unwrap().has_complete_ancestors() {
636
+ let cached = declaration.as_namespace().unwrap().ancestors();
637
+ self.propagate_descendants(&mut context.descendants, &cached);
638
+ context.descendants.remove(&declaration_id);
639
+ return cached;
640
+ }
641
+
642
+ if !context.seen_ids.insert(declaration_id) {
643
+ // If we find a cycle when linearizing ancestors, it's an error that the programmer must fix. However, we try to
644
+ // still approximate features by assuming that it must inherit from `Object` at some point (which is what most
645
+ // classes/modules inherit from). This is not 100% correct, but it allows us to provide a bit better IDE support
646
+ // for these cases
647
+ let estimated_ancestors = if matches!(declaration, Declaration::Namespace(Namespace::Class(_))) {
648
+ Ancestors::Cyclic(vec![Ancestor::Complete(*OBJECT_ID)])
649
+ } else {
650
+ Ancestors::Cyclic(vec![])
651
+ };
652
+ declaration
653
+ .as_namespace_mut()
654
+ .unwrap()
655
+ .set_ancestors(estimated_ancestors.clone());
656
+ context.descendants.remove(&declaration_id);
657
+ return estimated_ancestors;
658
+ }
659
+
660
+ // Automatically track descendants as we recurse. This has to happen before checking the cache since we may have
661
+ // already linearized the parent's ancestors, but it's the first time we're discovering the descendant
662
+ for descendant in &context.descendants {
663
+ self.graph
664
+ .declarations_mut()
665
+ .get_mut(&declaration_id)
666
+ .unwrap()
667
+ .as_namespace_mut()
668
+ .unwrap()
669
+ .add_descendant(*descendant);
670
+ }
671
+ }
672
+
673
+ // TODO: this check is against `Object` for now to avoid infinite recursion. After RBS indexing, we need to change
674
+ // this to `BasicObject` since it's the only class that cannot have a parent
675
+ let parent_ancestors = self.linearize_parent_ancestors(declaration_id, context);
676
+
677
+ let declaration = self.graph.declarations().get(&declaration_id).unwrap();
678
+
679
+ let mut mixins = Vec::new();
680
+
681
+ // If we're linearizing a singleton class, add the extends of the attached class to the list of mixins to process
682
+ if let Declaration::Namespace(Namespace::SingletonClass(_)) = declaration {
683
+ let attached_decl = self.graph.declarations().get(declaration.owner_id()).unwrap();
684
+
685
+ mixins.extend(
686
+ attached_decl
687
+ .definitions()
688
+ .iter()
689
+ .filter_map(|definition_id| self.mixins_of(*definition_id))
690
+ .flatten()
691
+ .filter(|mixin| matches!(mixin, Mixin::Extend(_))),
692
+ );
693
+ }
694
+
695
+ // Consider only prepends and includes for the current declaration
696
+ mixins.extend(
697
+ declaration
698
+ .definitions()
699
+ .iter()
700
+ .filter_map(|definition_id| self.mixins_of(*definition_id))
701
+ .flatten()
702
+ .filter(|mixin| matches!(mixin, Mixin::Prepend(_) | Mixin::Include(_))),
703
+ );
704
+
705
+ let (linearized_prepends, linearized_includes) =
706
+ self.linearize_mixins(context, mixins, parent_ancestors.as_ref());
707
+
708
+ // Build the final list
709
+ let mut ancestors = Vec::new();
710
+ ancestors.extend(linearized_prepends);
711
+ ancestors.push(Ancestor::Complete(declaration_id));
712
+ ancestors.extend(linearized_includes);
713
+ if let Some(parents) = parent_ancestors {
714
+ ancestors.extend(parents);
715
+ }
716
+
717
+ let result = if context.cyclic {
718
+ Ancestors::Cyclic(ancestors)
719
+ } else if context.partial {
720
+ Ancestors::Partial(ancestors)
721
+ } else {
722
+ Ancestors::Complete(ancestors)
723
+ };
724
+ self.graph
725
+ .declarations_mut()
726
+ .get_mut(&declaration_id)
727
+ .unwrap()
728
+ .as_namespace_mut()
729
+ .unwrap()
730
+ .set_ancestors(result.clone());
731
+
732
+ context.descendants.remove(&declaration_id);
733
+ result
734
+ }
735
+
736
+ fn linearize_parent_ancestors(
737
+ &mut self,
738
+ declaration_id: DeclarationId,
739
+ context: &mut LinearizationContext,
740
+ ) -> Option<Vec<Ancestor>> {
741
+ if declaration_id == *OBJECT_ID {
742
+ return None;
743
+ }
744
+
745
+ let declaration = self.graph.declarations().get(&declaration_id).unwrap();
746
+
747
+ match declaration {
748
+ Declaration::Namespace(Namespace::Class(_)) => {
749
+ let definition_ids = declaration.definitions().to_vec();
750
+
751
+ Some(match self.linearize_parent_class(&definition_ids, context) {
752
+ Ancestors::Complete(ids) => ids,
753
+ Ancestors::Cyclic(ids) => {
754
+ context.cyclic = true;
755
+ ids
756
+ }
757
+ Ancestors::Partial(ids) => {
758
+ context.partial = true;
759
+ ids
760
+ }
761
+ })
762
+ }
763
+ Declaration::Namespace(Namespace::SingletonClass(_)) => {
764
+ let owner_id = *declaration.owner_id();
765
+
766
+ let (singleton_parent_id, partial_singleton) = self.singleton_parent_id(owner_id);
767
+ if partial_singleton {
768
+ context.partial = true;
769
+ }
770
+
771
+ Some(match self.linearize_ancestors(singleton_parent_id, context) {
772
+ Ancestors::Complete(ids) => ids,
773
+ Ancestors::Cyclic(ids) => {
774
+ context.cyclic = true;
775
+ ids
776
+ }
777
+ Ancestors::Partial(ids) => {
778
+ context.partial = true;
779
+ ids
780
+ }
781
+ })
782
+ }
783
+ _ => None,
784
+ }
785
+ }
786
+
787
+ /// Linearize all mixins into a prepend and include list. This function requires the parent ancestors because included
788
+ /// modules are deduplicated against them
789
+ fn linearize_mixins(
790
+ &mut self,
791
+ context: &mut LinearizationContext,
792
+ mixins: Vec<Mixin>,
793
+ parent_ancestors: Option<&Vec<Ancestor>>,
794
+ ) -> (VecDeque<Ancestor>, VecDeque<Ancestor>) {
795
+ let mut linearized_prepends = VecDeque::new();
796
+ let mut linearized_includes = VecDeque::new();
797
+
798
+ // IMPORTANT! In the slice of mixins we receive, extends are the ones that occurred in the attached object, which we
799
+ // collect ahead of time. This is the reason why we apparently treat an extend like an include, because an extend in
800
+ // the attached object is equivalent to an include in the singleton class
801
+ for mixin in mixins {
802
+ let constant_reference = self
803
+ .graph
804
+ .constant_references()
805
+ .get(mixin.constant_reference_id())
806
+ .unwrap();
807
+
808
+ match mixin {
809
+ Mixin::Prepend(_) => {
810
+ match self.graph.names().get(constant_reference.name_id()).unwrap() {
811
+ NameRef::Resolved(resolved) => {
812
+ let ids = match self.linearize_ancestors(*resolved.declaration_id(), context) {
813
+ Ancestors::Complete(ids) => ids,
814
+ Ancestors::Cyclic(ids) => {
815
+ context.cyclic = true;
816
+ ids
817
+ }
818
+ Ancestors::Partial(ids) => {
819
+ context.partial = true;
820
+ ids
821
+ }
822
+ };
823
+
824
+ // Only reorder if there are new modules to add. If all modules being
825
+ // prepended are already in the chain (e.g., `prepend A` when A is already
826
+ // prepended via B), Ruby treats it as a no-op and keeps the existing order.
827
+ if ids.iter().any(|id| !linearized_prepends.contains(id)) {
828
+ // Remove existing entries that will be re-added from the new chain
829
+ linearized_prepends.retain(|id| !ids.contains(id));
830
+
831
+ for id in ids.into_iter().rev() {
832
+ linearized_prepends.push_front(id);
833
+ }
834
+ }
835
+ }
836
+ NameRef::Unresolved(_) => {
837
+ // We haven't been able to resolve this name yet, so we push it as a partial linearization to finish
838
+ // later
839
+ context.partial = true;
840
+ linearized_prepends.push_front(Ancestor::Partial(*constant_reference.name_id()));
841
+ }
842
+ }
843
+ }
844
+ Mixin::Include(_) | Mixin::Extend(_) => {
845
+ match self.graph.names().get(constant_reference.name_id()).unwrap() {
846
+ NameRef::Resolved(resolved) => {
847
+ let mut ids = match self.linearize_ancestors(*resolved.declaration_id(), context) {
848
+ Ancestors::Complete(ids) => ids,
849
+ Ancestors::Cyclic(ids) => {
850
+ context.cyclic = true;
851
+ ids
852
+ }
853
+ Ancestors::Partial(ids) => {
854
+ context.partial = true;
855
+ ids
856
+ }
857
+ };
858
+
859
+ // Prepended module are deduped based only on other prepended modules
860
+ ids.retain(|id| {
861
+ !linearized_prepends.contains(id)
862
+ && !linearized_includes.contains(id)
863
+ && parent_ancestors
864
+ .as_ref()
865
+ .is_none_or(|parent_ids| !parent_ids.contains(id))
866
+ });
867
+
868
+ for id in ids.into_iter().rev() {
869
+ linearized_includes.push_front(id);
870
+ }
871
+ }
872
+ NameRef::Unresolved(_) => {
873
+ // We haven't been able to resolve this name yet, so we push it as a partial linearization to finish
874
+ // later
875
+ context.partial = true;
876
+ linearized_includes.push_front(Ancestor::Partial(*constant_reference.name_id()));
877
+ }
878
+ }
879
+ }
880
+ }
881
+ }
882
+
883
+ (linearized_prepends, linearized_includes)
884
+ }
885
+
886
+ /// Propagate descendants to all cached ancestors
887
+ fn propagate_descendants<S: BuildHasher>(
888
+ &mut self,
889
+ descendants: &mut HashSet<DeclarationId, S>,
890
+ cached: &Ancestors,
891
+ ) {
892
+ if !descendants.is_empty() {
893
+ for ancestor in cached {
894
+ if let Ancestor::Complete(ancestor_id) = ancestor {
895
+ for descendant in descendants.iter() {
896
+ self.graph
897
+ .declarations_mut()
898
+ .get_mut(ancestor_id)
899
+ .unwrap()
900
+ .as_namespace_mut()
901
+ .unwrap()
902
+ .add_descendant(*descendant);
903
+ }
904
+ }
905
+ }
906
+ }
907
+ }
908
+
909
+ // Handles the resolution of the namespace name, the creation of the declaration and membership
910
+ fn handle_constant_declaration<F>(
911
+ &mut self,
912
+ name_id: NameId,
913
+ definition_id: DefinitionId,
914
+ singleton: bool,
915
+ declaration_builder: F,
916
+ ) -> Outcome
917
+ where
918
+ F: FnOnce(String, DeclarationId) -> Declaration,
919
+ {
920
+ let name_ref = self.graph.names().get(&name_id).unwrap();
921
+ let str_id = *name_ref.str();
922
+
923
+ // The name of the declaration is determined by the name of its owner, which may or may not require resolution
924
+ // depending on whether the name has a parent scope
925
+ match self.name_owner_id(name_id) {
926
+ Outcome::Resolved(owner_id, id_needing_linearization) => {
927
+ let mut fully_qualified_name = self.graph.strings().get(&str_id).unwrap().to_string();
928
+
929
+ {
930
+ let owner = self.graph.declarations().get(&owner_id).unwrap();
931
+
932
+ // We don't prefix declarations with `Object::`
933
+ if owner_id != *OBJECT_ID {
934
+ fully_qualified_name.insert_str(0, "::");
935
+ fully_qualified_name.insert_str(0, owner.name());
936
+ }
937
+ }
938
+
939
+ let declaration_id = DeclarationId::from(&fully_qualified_name);
940
+
941
+ if singleton {
942
+ self.graph
943
+ .declarations_mut()
944
+ .get_mut(&owner_id)
945
+ .unwrap()
946
+ .as_namespace_mut()
947
+ .unwrap()
948
+ .set_singleton_class_id(declaration_id);
949
+ } else {
950
+ self.graph.add_member(&owner_id, declaration_id, str_id);
951
+ }
952
+
953
+ self.graph.add_declaration(declaration_id, definition_id, || {
954
+ declaration_builder(fully_qualified_name, owner_id)
955
+ });
956
+ self.graph.record_resolved_name(name_id, declaration_id);
957
+ Outcome::Resolved(declaration_id, id_needing_linearization)
958
+ }
959
+ other => other,
960
+ }
961
+ }
962
+
963
+ // Returns the owner declaration ID for a given name. If the name is simple and has no parent scope, then the owner is
964
+ // either the nesting or Object. If the name has a parent scope, we attempt to resolve the reference and that should be
965
+ // the name's owner. For aliases, resolves through to get the actual namespace.
966
+ fn name_owner_id(&mut self, name_id: NameId) -> Outcome {
967
+ let name_ref = self.graph.names().get(&name_id).unwrap();
968
+
969
+ if let Some(parent_scope) = name_ref.parent_scope() {
970
+ // If we have `A::B`, the owner of `B` is whatever `A` resolves to.
971
+ // If `A` is an alias, resolve through to get the actual namespace.
972
+ match self.resolve_constant_internal(*parent_scope) {
973
+ Outcome::Resolved(id, linearization) => self.resolve_to_primary_namespace(id, linearization),
974
+ other => other,
975
+ }
976
+ } else if let Some(nesting_id) = name_ref.nesting() {
977
+ // Lexical nesting from block structure, e.g.:
978
+ // class ALIAS::Target
979
+ // CONST = 1 # CONST's nesting is the class, which may resolve to an alias target
980
+ // end
981
+ // If `ALIAS` points to `Outer`, `CONST` should be owned by `Outer::Target`, not `ALIAS::Target`.
982
+ match self.graph.names().get(nesting_id).unwrap() {
983
+ NameRef::Resolved(resolved) => self.resolve_to_primary_namespace(*resolved.declaration_id(), None),
984
+ NameRef::Unresolved(_) => {
985
+ // The only case where we wouldn't have the nesting resolved at this point is if it's available through
986
+ // inheritance or if it doesn't exist, so we need to retry later
987
+ Outcome::Retry
988
+ }
989
+ }
990
+ } else {
991
+ // Any constants at the top level are owned by Object
992
+ Outcome::Resolved(*OBJECT_ID, None)
993
+ }
994
+ }
995
+
996
+ /// Resolves a declaration ID through any alias chain to get the primary (first) namespace.
997
+ /// Returns `Retry` if the primary alias target hasn't been resolved yet.
998
+ fn resolve_to_primary_namespace(
999
+ &self,
1000
+ declaration_id: DeclarationId,
1001
+ linearization: Option<DeclarationId>,
1002
+ ) -> Outcome {
1003
+ let resolved_ids = self.resolve_alias_chains(declaration_id);
1004
+
1005
+ // Get the primary (first) resolved target
1006
+ let Some(&primary_id) = resolved_ids.first() else {
1007
+ return Outcome::Retry;
1008
+ };
1009
+
1010
+ // Check if the primary result is still an unresolved alias
1011
+ if matches!(
1012
+ self.graph.declarations().get(&primary_id),
1013
+ Some(Declaration::ConstantAlias(_))
1014
+ ) {
1015
+ return Outcome::Retry;
1016
+ }
1017
+
1018
+ Outcome::Resolved(primary_id, linearization)
1019
+ }
1020
+
1021
+ /// Attempts to resolve a constant reference against the graph. Returns the fully qualified declaration ID that the
1022
+ /// reference is related to or `None`. This method mutates the graph to remember which constants have already been
1023
+ /// resolved
1024
+ fn resolve_constant_internal(&mut self, name_id: NameId) -> Outcome {
1025
+ let name_ref = self.graph.names().get(&name_id).unwrap().clone();
1026
+
1027
+ match name_ref {
1028
+ NameRef::Unresolved(name) => {
1029
+ // If there's a parent scope for this constant, it means it's a constant path. We must first resolve the
1030
+ // outer most parent, so that we can then continue resolution from there, recording the results along the
1031
+ // way
1032
+ if let Some(parent_scope_id) = name.parent_scope() {
1033
+ if let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() {
1034
+ let declaration = self.graph.declarations().get(parent_scope.declaration_id()).unwrap();
1035
+
1036
+ // TODO: this is a temporary hack. We need to implement proper handling for `Struct.new`, `Class.new` and
1037
+ // `Module.new`, which now seem like constants, but are actually namespaces
1038
+ if matches!(declaration, Declaration::Constant(_)) {
1039
+ return Outcome::Unresolved(None);
1040
+ }
1041
+
1042
+ // Resolve the namespace in case it's an alias (e.g., ALIAS::CONST where ALIAS = Foo)
1043
+ // An alias can have multiple targets, so we try all of them in order.
1044
+ let resolved_ids = self.resolve_alias_chains(*parent_scope.declaration_id());
1045
+
1046
+ // Search each resolved target for the constant. Return early if found.
1047
+ let mut missing_linearization_id = None;
1048
+ let mut found_namespace = false;
1049
+ for &id in &resolved_ids {
1050
+ match self.graph.declarations().get(&id) {
1051
+ Some(Declaration::ConstantAlias(_)) => {
1052
+ // Alias not fully resolved yet
1053
+ return Outcome::Retry;
1054
+ }
1055
+ Some(Declaration::Namespace(_)) => {
1056
+ found_namespace = true;
1057
+ match self.search_ancestors(id, *name.str()) {
1058
+ Outcome::Resolved(declaration_id, _) => {
1059
+ self.graph.record_resolved_name(name_id, declaration_id);
1060
+ return Outcome::Resolved(declaration_id, None);
1061
+ }
1062
+ Outcome::Unresolved(Some(needs_linearization_id)) => {
1063
+ missing_linearization_id.get_or_insert(needs_linearization_id);
1064
+ }
1065
+ Outcome::Unresolved(None) => {}
1066
+ Outcome::Retry => unreachable!("search_ancestors never returns Retry"),
1067
+ }
1068
+ }
1069
+ _ => {
1070
+ // Not a namespace (e.g., a constant) - skip
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ // If no namespaces were found, this constant path can never resolve.
1076
+ if !found_namespace {
1077
+ return Outcome::Unresolved(None);
1078
+ }
1079
+
1080
+ // Member not found in any namespace yet - retry in case it's added later
1081
+ return missing_linearization_id.map_or(Outcome::Retry, |id| Outcome::Unresolved(Some(id)));
1082
+ }
1083
+
1084
+ return Outcome::Retry;
1085
+ }
1086
+
1087
+ // Otherwise, it's a simple constant read and we can resolve it directly
1088
+ let result = self.run_resolution(&name);
1089
+
1090
+ if let Outcome::Resolved(declaration_id, _) = result {
1091
+ self.graph.record_resolved_name(name_id, declaration_id);
1092
+ }
1093
+
1094
+ result
1095
+ }
1096
+ NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id(), None),
1097
+ }
1098
+ }
1099
+
1100
+ /// Resolves an alias chain to get all possible final target declarations.
1101
+ /// Returns the original ID if it's not an alias or if the target hasn't been resolved yet.
1102
+ ///
1103
+ /// When an alias has multiple definitions with different targets (e.g., conditional assignment),
1104
+ /// this returns all possible final targets.
1105
+ fn resolve_alias_chains(&self, declaration_id: DeclarationId) -> Vec<DeclarationId> {
1106
+ let mut results = Vec::new();
1107
+ let mut queue = VecDeque::from([declaration_id]);
1108
+ let mut seen = HashSet::new();
1109
+
1110
+ // Use BFS (pop_front) to preserve the order of alias targets.
1111
+ // The first target of an alias should remain the first/primary result.
1112
+ while let Some(current) = queue.pop_front() {
1113
+ if !seen.insert(current) {
1114
+ // Already processed or cycle detected
1115
+ continue;
1116
+ }
1117
+
1118
+ match self.graph.declarations().get(&current) {
1119
+ Some(Declaration::ConstantAlias(_)) => {
1120
+ let targets = self.graph.alias_targets(&current).unwrap_or_default();
1121
+ if targets.is_empty() {
1122
+ // Target not resolved yet, keep the alias for retry
1123
+ results.push(current);
1124
+ } else {
1125
+ queue.extend(targets);
1126
+ }
1127
+ }
1128
+ Some(_) => {
1129
+ // Not an alias, this is a final target
1130
+ results.push(current);
1131
+ }
1132
+ None => {
1133
+ panic!("Declaration {current:?} not found in graph");
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ results
1139
+ }
1140
+
1141
+ fn run_resolution(&mut self, name: &Name) -> Outcome {
1142
+ let str_id = *name.str();
1143
+ let mut missing_linearization_id = None;
1144
+
1145
+ if let Some(nesting) = name.nesting() {
1146
+ let scope_outcome = self.search_lexical_scopes(name, str_id);
1147
+
1148
+ // If we already resolved or need to retry, return early
1149
+ if scope_outcome.is_resolved_or_retry() {
1150
+ return scope_outcome;
1151
+ }
1152
+
1153
+ // Search inheritance chain
1154
+ let ancestor_outcome = match self.graph.names().get(nesting).unwrap() {
1155
+ NameRef::Resolved(nesting_name_ref) => {
1156
+ self.search_ancestors(*nesting_name_ref.declaration_id(), str_id)
1157
+ }
1158
+ NameRef::Unresolved(_) => Outcome::Retry,
1159
+ };
1160
+ match ancestor_outcome {
1161
+ Outcome::Resolved(_, _) | Outcome::Retry => return ancestor_outcome,
1162
+ Outcome::Unresolved(Some(needs_linearization_id)) => {
1163
+ missing_linearization_id = Some(needs_linearization_id);
1164
+ }
1165
+ Outcome::Unresolved(None) => {}
1166
+ }
1167
+ }
1168
+
1169
+ // If it's a top level reference starting with `::` or if we didn't find the constant anywhere else, the
1170
+ // fallback is the top level
1171
+ let outcome = self.search_top_level(str_id);
1172
+
1173
+ if let Some(linearization_id) = missing_linearization_id {
1174
+ match outcome {
1175
+ Outcome::Resolved(id, _) => Outcome::Resolved(id, Some(linearization_id)),
1176
+ Outcome::Unresolved(_) => Outcome::Unresolved(Some(linearization_id)),
1177
+ Outcome::Retry => {
1178
+ panic!("Retry shouldn't happen when searching the top level")
1179
+ }
1180
+ }
1181
+ } else {
1182
+ outcome
1183
+ }
1184
+ }
1185
+
1186
+ /// Search for a member in a declaration's ancestor chain.
1187
+ fn search_ancestors(&mut self, declaration_id: DeclarationId, str_id: StringId) -> Outcome {
1188
+ match self.ancestors_of(declaration_id) {
1189
+ Ancestors::Complete(ids) | Ancestors::Cyclic(ids) => ids
1190
+ .iter()
1191
+ .find_map(|ancestor_id| {
1192
+ if let Ancestor::Complete(ancestor_id) = ancestor_id {
1193
+ self.graph
1194
+ .declarations()
1195
+ .get(ancestor_id)
1196
+ .unwrap()
1197
+ .as_namespace()
1198
+ .unwrap()
1199
+ .member(&str_id)
1200
+ .map(|id| Outcome::Resolved(*id, None))
1201
+ } else {
1202
+ None
1203
+ }
1204
+ })
1205
+ .unwrap_or(Outcome::Unresolved(None)),
1206
+ Ancestors::Partial(ids) => ids
1207
+ .iter()
1208
+ .find_map(|ancestor_id| {
1209
+ if let Ancestor::Complete(ancestor_id) = ancestor_id {
1210
+ self.graph
1211
+ .declarations()
1212
+ .get(ancestor_id)
1213
+ .unwrap()
1214
+ .as_namespace()
1215
+ .unwrap()
1216
+ .member(&str_id)
1217
+ .map(|id| Outcome::Resolved(*id, Some(declaration_id)))
1218
+ } else {
1219
+ None
1220
+ }
1221
+ })
1222
+ .unwrap_or(Outcome::Unresolved(Some(declaration_id))),
1223
+ }
1224
+ }
1225
+
1226
+ /// Look for the constant in the lexical scopes that are a part of its nesting
1227
+ fn search_lexical_scopes(&self, name: &Name, str_id: StringId) -> Outcome {
1228
+ let mut current_name = name;
1229
+
1230
+ while let Some(nesting_id) = current_name.nesting() {
1231
+ if let NameRef::Resolved(nesting_name_ref) = self.graph.names().get(nesting_id).unwrap() {
1232
+ if let Some(declaration) = self.graph.declarations().get(nesting_name_ref.declaration_id())
1233
+ && !matches!(declaration, Declaration::Constant(_) | Declaration::ConstantAlias(_)) // TODO: temporary hack to avoid crashing on `Struct.new`
1234
+ && let Some(member) = declaration.as_namespace().unwrap().member(&str_id)
1235
+ {
1236
+ return Outcome::Resolved(*member, None);
1237
+ }
1238
+
1239
+ current_name = nesting_name_ref.name();
1240
+ } else {
1241
+ return Outcome::Retry;
1242
+ }
1243
+ }
1244
+
1245
+ Outcome::Unresolved(None)
1246
+ }
1247
+
1248
+ /// Look for the constant at the top level (member of Object)
1249
+ fn search_top_level(&self, str_id: StringId) -> Outcome {
1250
+ match self
1251
+ .graph
1252
+ .declarations()
1253
+ .get(&OBJECT_ID)
1254
+ .unwrap()
1255
+ .as_namespace()
1256
+ .unwrap()
1257
+ .member(&str_id)
1258
+ {
1259
+ Some(member_id) => Outcome::Resolved(*member_id, None),
1260
+ None => Outcome::Unresolved(None),
1261
+ }
1262
+ }
1263
+
1264
+ /// Returns a complexity score for a given name, which is used to sort names for resolution. The complexity is based
1265
+ /// on how many parent scopes are involved in a name's nesting. This is because simple names are always
1266
+ /// straightforward to resolve no matter how deep the nesting is. For example:
1267
+ ///
1268
+ /// ```ruby
1269
+ /// module Foo
1270
+ /// module Bar
1271
+ /// class Baz; end
1272
+ /// end
1273
+ /// end
1274
+ /// ```
1275
+ ///
1276
+ /// These are all simple names because they don't require resolution logic to determine the final name of each step.
1277
+ /// We only have to ensure that they are ordered by nesting level. Names with parent scopes require that their parts
1278
+ /// be resolved to determine what they refer to and so they must be sorted last.
1279
+ ///
1280
+ /// ```ruby
1281
+ /// module Foo
1282
+ /// module Bar::Baz
1283
+ /// class Qux; end
1284
+ /// end
1285
+ /// end
1286
+ /// ```
1287
+ ///
1288
+ /// In this case, we need `Bar` to have already been processed so that we can resolve the `Bar` reference inside of
1289
+ /// the `Foo` nesting, which then unblocks the resolution of `Baz` and finally `Qux`. Notice how `Qux` is a simple
1290
+ /// name, but it's nested under a complex name so we have to sort it last. This is why we consider the number of
1291
+ /// parent scopes in the entire nesting, not just for the name itself
1292
+ ///
1293
+ /// # Panics
1294
+ ///
1295
+ /// Will panic if there is inconsistent data in the graph
1296
+ fn name_depth(name: &NameRef, names: &IdentityHashMap<NameId, NameRef>) -> u32 {
1297
+ let parent_depth = name.parent_scope().map_or(0, |id| {
1298
+ let name_ref = names.get(&id).unwrap();
1299
+ Self::name_depth(name_ref, names)
1300
+ });
1301
+ let nesting_depth = name.nesting().map_or(0, |id| {
1302
+ let name_ref = names.get(&id).unwrap();
1303
+ Self::name_depth(name_ref, names)
1304
+ });
1305
+
1306
+ parent_depth + nesting_depth + 1
1307
+ }
1308
+
1309
+ /// Returns a tuple of 2 vectors:
1310
+ /// - The first one contains all constants, sorted in order for resolution (less complex constant names first)
1311
+ /// - The second one contains all other definitions, in no particular order
1312
+ #[must_use]
1313
+ fn sorted_units(&self) -> (VecDeque<Unit>, Vec<DefinitionId>) {
1314
+ let estimated_length = self.graph.definitions().len() / 2;
1315
+ let mut definitions = Vec::with_capacity(estimated_length);
1316
+ let mut others = Vec::with_capacity(estimated_length);
1317
+ let names = self.graph.names();
1318
+
1319
+ for (id, definition) in self.graph.definitions() {
1320
+ let uri = self.graph.documents().get(definition.uri_id()).unwrap().uri();
1321
+
1322
+ match definition {
1323
+ Definition::Class(def) => {
1324
+ definitions.push((
1325
+ Unit::Definition(*id),
1326
+ (names.get(def.name_id()).unwrap(), uri, definition.offset()),
1327
+ ));
1328
+ }
1329
+ Definition::Module(def) => {
1330
+ definitions.push((
1331
+ Unit::Definition(*id),
1332
+ (names.get(def.name_id()).unwrap(), uri, definition.offset()),
1333
+ ));
1334
+ }
1335
+ Definition::Constant(def) => {
1336
+ definitions.push((
1337
+ Unit::Definition(*id),
1338
+ (names.get(def.name_id()).unwrap(), uri, definition.offset()),
1339
+ ));
1340
+ }
1341
+ Definition::ConstantAlias(def) => {
1342
+ definitions.push((
1343
+ Unit::Definition(*id),
1344
+ (names.get(def.name_id()).unwrap(), uri, definition.offset()),
1345
+ ));
1346
+ }
1347
+ Definition::SingletonClass(def) => {
1348
+ definitions.push((
1349
+ Unit::Definition(*id),
1350
+ (names.get(def.name_id()).unwrap(), uri, definition.offset()),
1351
+ ));
1352
+ }
1353
+ _ => {
1354
+ others.push(*id);
1355
+ }
1356
+ }
1357
+ }
1358
+
1359
+ // Sort namespaces based on their name complexity so that simpler names are always first
1360
+ // When the depth is the same, sort by URI and offset to maintain determinism
1361
+ definitions.sort_by(|(_, (name_a, uri_a, offset_a)), (_, (name_b, uri_b, offset_b))| {
1362
+ (Self::name_depth(name_a, names), uri_a, offset_a).cmp(&(Self::name_depth(name_b, names), uri_b, offset_b))
1363
+ });
1364
+
1365
+ let mut references = self
1366
+ .graph
1367
+ .constant_references()
1368
+ .iter()
1369
+ .map(|(id, constant_ref)| {
1370
+ let uri = self.graph.documents().get(&constant_ref.uri_id()).unwrap().uri();
1371
+
1372
+ (
1373
+ Unit::Reference(*id),
1374
+ (names.get(constant_ref.name_id()).unwrap(), uri, constant_ref.offset()),
1375
+ )
1376
+ })
1377
+ .collect::<Vec<_>>();
1378
+
1379
+ // Sort constant references based on their name complexity so that simpler names are always first
1380
+ references.sort_by(|(_, (name_a, uri_a, offset_a)), (_, (name_b, uri_b, offset_b))| {
1381
+ (Self::name_depth(name_a, names), uri_a, offset_a).cmp(&(Self::name_depth(name_b, names), uri_b, offset_b))
1382
+ });
1383
+
1384
+ let mut units = definitions.into_iter().map(|(id, _)| id).collect::<VecDeque<_>>();
1385
+ units.extend(references.into_iter().map(|(id, _)| id).collect::<VecDeque<_>>());
1386
+
1387
+ others.shrink_to_fit();
1388
+
1389
+ (units, others)
1390
+ }
1391
+
1392
+ /// Returns the singleton parent ID for an attached object ID. A singleton class' parent depends on what the attached
1393
+ /// object is:
1394
+ ///
1395
+ /// - Module: parent is the `Module` class
1396
+ /// - Class: parent is the singleton class of the original parent class
1397
+ /// - Singleton class: recurse as many times as necessary to wrap the original attached object's parent class
1398
+ fn singleton_parent_id(&mut self, attached_id: DeclarationId) -> (DeclarationId, bool) {
1399
+ // Base case: if we reached `Object`, then the parent is `Class`
1400
+ if attached_id == *OBJECT_ID {
1401
+ return (*CLASS_ID, false);
1402
+ }
1403
+
1404
+ let decl = self.graph.declarations().get(&attached_id).unwrap();
1405
+
1406
+ match decl {
1407
+ Declaration::Namespace(Namespace::Module(_)) => (*MODULE_ID, false),
1408
+ Declaration::Namespace(Namespace::SingletonClass(_)) => {
1409
+ // For singleton classes, we keep recursively wrapping parents until we can reach the original attached
1410
+ // object
1411
+ let owner_id = *decl.owner_id();
1412
+
1413
+ let (inner_parent, partial) = self.singleton_parent_id(owner_id);
1414
+ (self.get_or_create_singleton_class(inner_parent), partial)
1415
+ }
1416
+ Declaration::Namespace(Namespace::Class(_)) => {
1417
+ // For classes (the regular case), we need to return the singleton class of its parent
1418
+ let definition_ids = decl.definitions().to_vec();
1419
+
1420
+ let (picked_parent, partial) = self.get_parent_class(&definition_ids);
1421
+ (self.get_or_create_singleton_class(picked_parent), partial)
1422
+ }
1423
+ _ => {
1424
+ // Other declaration types (constants, methods, etc.) shouldn't reach here,
1425
+ // but default to Object's singleton parent
1426
+ (*CLASS_ID, false)
1427
+ }
1428
+ }
1429
+ }
1430
+
1431
+ fn get_parent_class(&self, definition_ids: &[DefinitionId]) -> (DeclarationId, bool) {
1432
+ let mut explicit_parents = Vec::new();
1433
+ let mut partial = false;
1434
+
1435
+ for definition_id in definition_ids {
1436
+ let definition = self.graph.definitions().get(definition_id).unwrap();
1437
+
1438
+ if let Definition::Class(class) = definition
1439
+ && let Some(superclass) = class.superclass_ref()
1440
+ {
1441
+ let name = self
1442
+ .graph
1443
+ .names()
1444
+ .get(self.graph.constant_references().get(superclass).unwrap().name_id())
1445
+ .unwrap();
1446
+
1447
+ match name {
1448
+ NameRef::Resolved(resolved) => {
1449
+ explicit_parents.push(*resolved.declaration_id());
1450
+ }
1451
+ NameRef::Unresolved(_) => {
1452
+ partial = true;
1453
+ }
1454
+ }
1455
+ }
1456
+ }
1457
+
1458
+ // If there's more than one parent class that isn't `Object` and they are different, then there's a superclass
1459
+ // mismatch error. TODO: We should add a diagnostic here
1460
+ (explicit_parents.first().copied().unwrap_or(*OBJECT_ID), partial)
1461
+ }
1462
+
1463
+ fn linearize_parent_class(
1464
+ &mut self,
1465
+ definition_ids: &[DefinitionId],
1466
+ context: &mut LinearizationContext,
1467
+ ) -> Ancestors {
1468
+ let (picked_parent, partial) = self.get_parent_class(definition_ids);
1469
+ let result = self.linearize_ancestors(picked_parent, context);
1470
+ if partial { result.to_partial() } else { result }
1471
+ }
1472
+
1473
+ fn mixins_of(&self, definition_id: DefinitionId) -> Option<Vec<Mixin>> {
1474
+ let definition = self.graph.definitions().get(&definition_id).unwrap();
1475
+
1476
+ match definition {
1477
+ Definition::Class(class) => Some(class.mixins().to_vec()),
1478
+ Definition::SingletonClass(class) => Some(class.mixins().to_vec()),
1479
+ Definition::Module(module) => Some(module.mixins().to_vec()),
1480
+ _ => None,
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ #[cfg(test)]
1486
+ mod tests {
1487
+ use super::*;
1488
+ use crate::diagnostic::Rule;
1489
+ use crate::model::ids::UriId;
1490
+ use crate::test_utils::GraphTest;
1491
+
1492
+ macro_rules! assert_constant_alias_target_eq {
1493
+ ($context:expr, $alias_name:expr, $target_name:expr) => {{
1494
+ let decl_id = DeclarationId::from($alias_name);
1495
+ let target = $context
1496
+ .graph()
1497
+ .alias_targets(&decl_id)
1498
+ .and_then(|t| t.first().copied());
1499
+ assert_eq!(
1500
+ target,
1501
+ Some(DeclarationId::from($target_name)),
1502
+ "Expected alias '{}' to have primary target '{}'",
1503
+ $alias_name,
1504
+ $target_name
1505
+ );
1506
+ }};
1507
+ }
1508
+
1509
+ macro_rules! assert_no_constant_alias_target {
1510
+ ($context:expr, $alias_name:expr) => {{
1511
+ let decl_id = DeclarationId::from($alias_name);
1512
+ let targets = $context.graph().alias_targets(&decl_id).unwrap_or_default();
1513
+ assert!(
1514
+ targets.is_empty(),
1515
+ "Expected no alias target for '{}', but found {:?}",
1516
+ $alias_name,
1517
+ targets
1518
+ );
1519
+ }};
1520
+ }
1521
+
1522
+ macro_rules! assert_alias_targets_contain {
1523
+ ($context:expr, $alias_name:expr, $($target_name:expr),+ $(,)?) => {{
1524
+ let decl_id = DeclarationId::from($alias_name);
1525
+ let targets = $context.graph().alias_targets(&decl_id).unwrap_or_default();
1526
+ $(
1527
+ let expected_id = DeclarationId::from($target_name);
1528
+ assert!(
1529
+ targets.contains(&expected_id),
1530
+ "Expected alias '{}' to contain target '{}', but targets were {:?}",
1531
+ $alias_name,
1532
+ $target_name,
1533
+ targets
1534
+ );
1535
+ )+
1536
+ }};
1537
+ }
1538
+
1539
+ /// Asserts that a declaration has a constant reference at the specified location
1540
+ ///
1541
+ /// This macro:
1542
+ /// 1. Parses the location string into `(uri, start_offset, end_offset)`
1543
+ /// 2. Finds the declaration by name
1544
+ /// 3. Finds a constant reference to that declaration at the given uri and start offset
1545
+ /// 4. Asserts the end offset matches
1546
+ ///
1547
+ /// Location format: "uri:start_line:start_column-end_line:end_column"
1548
+ /// Example: `<file:///foo.rb:3:0-3:5>`
1549
+ macro_rules! assert_constant_reference_to {
1550
+ ($context:expr, $declaration_name:expr, $location:expr) => {
1551
+ let (uri, start, end) = $context.parse_location($location);
1552
+
1553
+ let declaration = $context
1554
+ .graph()
1555
+ .declarations()
1556
+ .get(&DeclarationId::from($declaration_name))
1557
+ .expect(&format!("Declaration '{}' not found in graph", $declaration_name));
1558
+
1559
+ let constant = declaration
1560
+ .references()
1561
+ .iter()
1562
+ .filter_map(|r| {
1563
+ let reference = $context
1564
+ .graph()
1565
+ .constant_references()
1566
+ .get(r)
1567
+ .expect("Reference should exist");
1568
+ if let NameRef::Resolved(_) = $context
1569
+ .graph()
1570
+ .names()
1571
+ .get(reference.name_id())
1572
+ .expect("Name should exist")
1573
+ {
1574
+ Some(reference)
1575
+ } else {
1576
+ None
1577
+ }
1578
+ })
1579
+ .find(|c| c.uri_id() == UriId::from(&uri) && c.offset().start() == start)
1580
+ .expect(&format!(
1581
+ "Declaration '{}' does not have a reference at {} starting at offset {}",
1582
+ $declaration_name, $location, start
1583
+ ));
1584
+
1585
+ $context.assert_offset_matches(
1586
+ &uri,
1587
+ constant.offset(),
1588
+ start,
1589
+ end,
1590
+ &format!("reference to '{}'", $declaration_name),
1591
+ $location,
1592
+ );
1593
+ };
1594
+ }
1595
+
1596
+ macro_rules! assert_ancestors_eq {
1597
+ ($context:expr, $name:expr, $expected:expr) => {
1598
+ let declaration = $context
1599
+ .graph()
1600
+ .declarations()
1601
+ .get(&DeclarationId::from($name))
1602
+ .unwrap();
1603
+
1604
+ match declaration.as_namespace().unwrap().ancestors() {
1605
+ Ancestors::Cyclic(ancestors) | Ancestors::Complete(ancestors) => {
1606
+ assert_eq!(
1607
+ $expected
1608
+ .iter()
1609
+ .map(|n| Ancestor::Complete(DeclarationId::from(*n)))
1610
+ .collect::<Vec<_>>(),
1611
+ ancestors,
1612
+ "Incorrect ancestors {}",
1613
+ ancestors
1614
+ .iter()
1615
+ .filter_map(|id| {
1616
+ if let Ancestor::Complete(id) = id {
1617
+ let name = {
1618
+ $context
1619
+ .graph()
1620
+ .declarations()
1621
+ .get(id)
1622
+ .unwrap()
1623
+ .name()
1624
+ .to_string()
1625
+ };
1626
+ Some(name)
1627
+ } else {
1628
+ None
1629
+ }
1630
+ })
1631
+ .collect::<Vec<_>>()
1632
+ .join(", ")
1633
+ );
1634
+ }
1635
+ Ancestors::Partial(_) => {
1636
+ panic!("Expected ancestors to be resolved for {}", declaration.name());
1637
+ }
1638
+ }
1639
+ };
1640
+ }
1641
+
1642
+ macro_rules! assert_descendants {
1643
+ ($context:expr, $parent:expr, $descendants:expr) => {
1644
+ let parent = $context
1645
+ .graph()
1646
+ .declarations()
1647
+ .get(&DeclarationId::from($parent))
1648
+ .unwrap();
1649
+ let actual = match parent {
1650
+ Declaration::Namespace(Namespace::Class(class)) => {
1651
+ class.descendants().iter().cloned().collect::<Vec<_>>()
1652
+ }
1653
+ Declaration::Namespace(Namespace::Module(module)) => {
1654
+ module.descendants().iter().cloned().collect::<Vec<_>>()
1655
+ }
1656
+ Declaration::Namespace(Namespace::SingletonClass(singleton)) => {
1657
+ singleton.descendants().iter().cloned().collect::<Vec<_>>()
1658
+ }
1659
+ _ => panic!("Tried to get descendants for a declaration that isn't a namespace"),
1660
+ };
1661
+
1662
+ for descendant in &$descendants {
1663
+ let descendant_id = DeclarationId::from(*descendant);
1664
+
1665
+ assert!(
1666
+ actual.contains(&descendant_id),
1667
+ "Expected '{}' to be a descendant of '{}'",
1668
+ $context
1669
+ .graph()
1670
+ .declarations()
1671
+ .get(&descendant_id)
1672
+ .unwrap()
1673
+ .name(),
1674
+ parent.name()
1675
+ );
1676
+ }
1677
+ };
1678
+ }
1679
+
1680
+ macro_rules! assert_members_eq {
1681
+ ($context:expr, $declaration_id:expr, $expected_members:expr) => {
1682
+ let mut actual_members = $context
1683
+ .graph()
1684
+ .declarations()
1685
+ .get(&DeclarationId::from($declaration_id))
1686
+ .unwrap()
1687
+ .as_namespace()
1688
+ .unwrap()
1689
+ .members()
1690
+ .iter()
1691
+ .map(|(str_id, _)| $context.graph().strings().get(str_id).unwrap().as_str())
1692
+ .collect::<Vec<_>>();
1693
+
1694
+ actual_members.sort();
1695
+
1696
+ assert_eq!($expected_members, actual_members);
1697
+ };
1698
+ }
1699
+
1700
+ macro_rules! assert_no_members {
1701
+ ($context:expr, $declaration_id:expr) => {
1702
+ assert_members_eq!($context, $declaration_id, vec![] as Vec<&str>);
1703
+ };
1704
+ }
1705
+
1706
+ macro_rules! assert_owner_eq {
1707
+ ($context:expr, $declaration_id:expr, $expected_owner_name:expr) => {
1708
+ let actual_owner_id = $context
1709
+ .graph()
1710
+ .declarations()
1711
+ .get(&DeclarationId::from($declaration_id))
1712
+ .unwrap()
1713
+ .owner_id();
1714
+
1715
+ let actual_owner_name = $context
1716
+ .graph()
1717
+ .declarations()
1718
+ .get(actual_owner_id)
1719
+ .unwrap()
1720
+ .name();
1721
+
1722
+ assert_eq!($expected_owner_name, actual_owner_name);
1723
+ };
1724
+ }
1725
+
1726
+ macro_rules! assert_singleton_class_eq {
1727
+ ($context:expr, $declaration_id:expr, $expected_singleton_class_name:expr) => {
1728
+ let declaration = $context
1729
+ .graph()
1730
+ .declarations()
1731
+ .get(&DeclarationId::from($declaration_id))
1732
+ .unwrap();
1733
+
1734
+ assert_eq!(
1735
+ $expected_singleton_class_name,
1736
+ $context
1737
+ .graph()
1738
+ .declarations()
1739
+ .get(declaration.as_namespace().unwrap().singleton_class().unwrap())
1740
+ .unwrap()
1741
+ .name()
1742
+ );
1743
+ };
1744
+ }
1745
+
1746
+ macro_rules! assert_instance_variables_eq {
1747
+ ($context:expr, $declaration_id:expr, $expected_instance_variables:expr) => {
1748
+ let mut actual_instance_variables = $context
1749
+ .graph()
1750
+ .declarations()
1751
+ .get(&DeclarationId::from($declaration_id))
1752
+ .unwrap()
1753
+ .as_namespace()
1754
+ .unwrap()
1755
+ .members()
1756
+ .iter()
1757
+ .filter_map(
1758
+ |(str_id, member_id)| match $context.graph().declarations().get(member_id) {
1759
+ Some(Declaration::InstanceVariable(_)) => {
1760
+ Some($context.graph().strings().get(str_id).unwrap().as_str())
1761
+ }
1762
+ _ => None,
1763
+ },
1764
+ )
1765
+ .collect::<Vec<_>>();
1766
+
1767
+ actual_instance_variables.sort();
1768
+
1769
+ assert_eq!($expected_instance_variables, actual_instance_variables);
1770
+ };
1771
+ }
1772
+
1773
+ fn format_diagnostics(context: &GraphTest, ignore_rules: &[Rule]) -> Vec<String> {
1774
+ let mut diagnostics: Vec<_> = context
1775
+ .graph()
1776
+ .all_diagnostics()
1777
+ .into_iter()
1778
+ .filter(|d| !ignore_rules.contains(d.rule()))
1779
+ .collect();
1780
+
1781
+ diagnostics.sort_by_key(|d| {
1782
+ let uri = context.graph().documents().get(d.uri_id()).unwrap().uri();
1783
+ (uri, d.offset())
1784
+ });
1785
+
1786
+ diagnostics
1787
+ .iter()
1788
+ .map(|d| {
1789
+ let document = context.graph().documents().get(d.uri_id()).unwrap();
1790
+ d.formatted(document)
1791
+ })
1792
+ .collect()
1793
+ }
1794
+
1795
+ macro_rules! assert_diagnostics_eq {
1796
+ ($context:expr, $expected_diagnostics:expr) => {{
1797
+ assert_eq!($expected_diagnostics, format_diagnostics($context, &[]));
1798
+ }};
1799
+ ($context:expr, $expected_diagnostics:expr, $ignore_rules:expr) => {{
1800
+ assert_eq!($expected_diagnostics, format_diagnostics($context, $ignore_rules));
1801
+ }};
1802
+ }
1803
+
1804
+ macro_rules! assert_no_diagnostics {
1805
+ ($context:expr) => {{
1806
+ let diagnostics = format_diagnostics($context, &[]);
1807
+ assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
1808
+ }};
1809
+ ($context:expr, $ignore_rules:expr) => {{
1810
+ let diagnostics = format_diagnostics($context, $ignore_rules);
1811
+ assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
1812
+ }};
1813
+ }
1814
+
1815
+ #[test]
1816
+ fn resolving_top_level_references() {
1817
+ let mut context = GraphTest::new();
1818
+ context.index_uri("file:///bar.rb", {
1819
+ r"
1820
+ class Bar; end
1821
+
1822
+ ::Bar
1823
+ Bar
1824
+ "
1825
+ });
1826
+ context.index_uri("file:///foo.rb", {
1827
+ r"
1828
+ module Foo
1829
+ ::Bar
1830
+ end
1831
+ "
1832
+ });
1833
+ context.resolve();
1834
+
1835
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
1836
+
1837
+ assert_constant_reference_to!(context, "Bar", "file:///bar.rb:2:2-2:5");
1838
+ assert_constant_reference_to!(context, "Bar", "file:///bar.rb:3:0-3:3");
1839
+ assert_constant_reference_to!(context, "Bar", "file:///foo.rb:1:4-1:7");
1840
+ }
1841
+
1842
+ #[test]
1843
+ fn resolving_nested_reference() {
1844
+ let mut context = GraphTest::new();
1845
+ context.index_uri("file:///bar.rb", {
1846
+ r"
1847
+ module Foo
1848
+ CONST = 123
1849
+
1850
+ class Bar
1851
+ CONST
1852
+ Foo::CONST
1853
+ end
1854
+ end
1855
+ "
1856
+ });
1857
+ context.resolve();
1858
+
1859
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///bar.rb:4:4-4:9");
1860
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///bar.rb:5:9-5:14");
1861
+ }
1862
+
1863
+ #[test]
1864
+ fn resolving_nested_reference_that_refer_to_top_level_constant() {
1865
+ let mut context = GraphTest::new();
1866
+ context.index_uri("file:///bar.rb", {
1867
+ r"
1868
+ class Baz; end
1869
+
1870
+ module Foo
1871
+ class Bar
1872
+ Baz
1873
+ end
1874
+ end
1875
+ "
1876
+ });
1877
+ context.resolve();
1878
+
1879
+ assert_no_diagnostics!(&context);
1880
+
1881
+ assert_constant_reference_to!(context, "Baz", "file:///bar.rb:4:4-4:7");
1882
+ }
1883
+
1884
+ #[test]
1885
+ fn resolving_constant_path_references_at_top_level() {
1886
+ let mut context = GraphTest::new();
1887
+ context.index_uri("file:///bar.rb", {
1888
+ r"
1889
+ module Foo
1890
+ class Bar; end
1891
+ end
1892
+
1893
+ Foo::Bar
1894
+ "
1895
+ });
1896
+ context.resolve();
1897
+
1898
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
1899
+
1900
+ assert_constant_reference_to!(context, "Foo::Bar", "file:///bar.rb:4:5-4:8");
1901
+ }
1902
+
1903
+ #[test]
1904
+ fn resolving_reference_for_non_existing_declaration() {
1905
+ let mut context = GraphTest::new();
1906
+ context.index_uri("file:///foo.rb", {
1907
+ r"
1908
+ Foo
1909
+ "
1910
+ });
1911
+ context.resolve();
1912
+
1913
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
1914
+
1915
+ let reference = context.graph().constant_references().values().next().unwrap();
1916
+
1917
+ match context.graph().names().get(reference.name_id()) {
1918
+ Some(NameRef::Unresolved(_)) => {}
1919
+ _ => panic!("expected unresolved constant reference"),
1920
+ }
1921
+ }
1922
+
1923
+ #[test]
1924
+ fn resolution_creates_global_declaration() {
1925
+ let mut context = GraphTest::new();
1926
+ context.index_uri("file:///foo.rb", {
1927
+ r"
1928
+ module Foo
1929
+ class Bar
1930
+ end
1931
+ end
1932
+
1933
+ class Foo::Baz
1934
+ end
1935
+ "
1936
+ });
1937
+ context.resolve();
1938
+
1939
+ assert_no_diagnostics!(&context);
1940
+
1941
+ assert_members_eq!(context, "Foo", vec!["Bar", "Baz"]);
1942
+ assert_owner_eq!(context, "Foo", "Object");
1943
+
1944
+ assert_no_members!(context, "Foo::Bar");
1945
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
1946
+
1947
+ assert_no_members!(context, "Foo::Baz");
1948
+ assert_owner_eq!(context, "Foo::Baz", "Foo");
1949
+ }
1950
+
1951
+ #[test]
1952
+ fn resolution_for_non_constant_declarations() {
1953
+ let mut context = GraphTest::new();
1954
+ context.index_uri("file:///foo.rb", {
1955
+ r"
1956
+ class Foo
1957
+ def initialize
1958
+ @name = 123
1959
+ end
1960
+ end
1961
+ "
1962
+ });
1963
+ context.resolve();
1964
+
1965
+ assert_no_diagnostics!(&context);
1966
+
1967
+ assert_members_eq!(context, "Foo", vec!["@name", "initialize()"]);
1968
+ assert_owner_eq!(context, "Foo", "Object");
1969
+ }
1970
+
1971
+ #[test]
1972
+ fn resolution_for_ambiguous_namespace_definitions() {
1973
+ // Like many examples of Ruby code that is ambiguous to static analysis, this example is ambiguous due to
1974
+ // require order. If `foo.rb` is loaded first, then `Bar` doesn't exist, Ruby crashes and we should emit an
1975
+ // error or warning for a non existing constant.
1976
+ //
1977
+ // If `bar.rb` is loaded first, then `Bar` resolves to top level `Bar` and `Bar::Baz` is defined, completely
1978
+ // escaping the `Foo` nesting.
1979
+ let mut context = GraphTest::new();
1980
+ context.index_uri("file:///foo.rb", {
1981
+ r"
1982
+ module Foo
1983
+ class Bar::Baz
1984
+ end
1985
+ end
1986
+ "
1987
+ });
1988
+ context.index_uri("file:///bar.rb", {
1989
+ r"
1990
+ module Bar
1991
+ end
1992
+ "
1993
+ });
1994
+ context.resolve();
1995
+
1996
+ assert_no_diagnostics!(&context);
1997
+
1998
+ assert_no_members!(context, "Foo");
1999
+ assert_owner_eq!(context, "Foo", "Object");
2000
+
2001
+ assert_members_eq!(context, "Bar", vec!["Baz"]);
2002
+ assert_owner_eq!(context, "Bar", "Object");
2003
+ }
2004
+
2005
+ #[test]
2006
+ fn resolution_for_top_level_references() {
2007
+ let mut context = GraphTest::new();
2008
+ context.index_uri("file:///foo.rb", {
2009
+ r"
2010
+ module Foo
2011
+ class ::Bar
2012
+ class Baz
2013
+ end
2014
+ end
2015
+ end
2016
+ "
2017
+ });
2018
+ context.resolve();
2019
+
2020
+ assert_no_diagnostics!(&context);
2021
+
2022
+ assert_no_members!(context, "Foo");
2023
+ assert_owner_eq!(context, "Foo", "Object");
2024
+
2025
+ assert_members_eq!(context, "Bar", vec!["Baz"]);
2026
+ assert_owner_eq!(context, "Bar", "Object");
2027
+
2028
+ assert_no_members!(context, "Bar::Baz");
2029
+ assert_owner_eq!(context, "Bar::Baz", "Bar");
2030
+ }
2031
+
2032
+ #[test]
2033
+ fn resolution_does_not_loop_infinitely_on_non_existing_constants() {
2034
+ let mut context = GraphTest::new();
2035
+ context.index_uri("file:///foo.rb", {
2036
+ r"
2037
+ class Foo::Bar
2038
+ class Baz
2039
+ end
2040
+ end
2041
+ "
2042
+ });
2043
+ context.resolve();
2044
+ assert!(
2045
+ context
2046
+ .graph()
2047
+ .declarations()
2048
+ .get(&DeclarationId::from("Foo"))
2049
+ .is_none()
2050
+ );
2051
+ assert!(
2052
+ context
2053
+ .graph()
2054
+ .declarations()
2055
+ .get(&DeclarationId::from("Foo::Bar"))
2056
+ .is_none()
2057
+ );
2058
+ assert!(
2059
+ context
2060
+ .graph()
2061
+ .declarations()
2062
+ .get(&DeclarationId::from("Foo::Bar::Baz"))
2063
+ .is_none()
2064
+ );
2065
+
2066
+ assert_no_diagnostics!(&context);
2067
+ }
2068
+
2069
+ #[test]
2070
+ fn expected_name_depth_order() {
2071
+ let mut context = GraphTest::new();
2072
+ context.index_uri("file:///foo.rb", {
2073
+ r"
2074
+ module Foo
2075
+ module Bar
2076
+ module Baz
2077
+ end
2078
+
2079
+ module ::Top
2080
+ class AfterTop
2081
+ end
2082
+ end
2083
+ end
2084
+
2085
+ module Qux::Zip
2086
+ module Zap
2087
+ class Zop::Boop
2088
+ end
2089
+ end
2090
+ end
2091
+ end
2092
+ "
2093
+ });
2094
+
2095
+ let mut names = context.graph().names().values().collect::<Vec<_>>();
2096
+ assert_eq!(10, names.len());
2097
+
2098
+ names.sort_by_key(|a| Resolver::name_depth(a, context.graph().names()));
2099
+
2100
+ assert_eq!(
2101
+ vec![
2102
+ "Top", "Foo", "Qux", "AfterTop", "Bar", "Baz", "Zip", "Zap", "Zop", "Boop"
2103
+ ],
2104
+ names
2105
+ .iter()
2106
+ .map(|n| context.graph().strings().get(n.str()).unwrap().as_str())
2107
+ .collect::<Vec<_>>()
2108
+ );
2109
+ }
2110
+
2111
+ #[test]
2112
+ fn resolution_for_singleton_class() {
2113
+ let mut context = GraphTest::new();
2114
+ context.index_uri("file:///foo.rb", {
2115
+ r"
2116
+ class Foo
2117
+ class << self
2118
+ def bar; end
2119
+ BAZ = 123
2120
+ end
2121
+ end
2122
+ "
2123
+ });
2124
+ context.resolve();
2125
+
2126
+ assert_no_diagnostics!(&context);
2127
+
2128
+ assert_no_members!(context, "Foo");
2129
+ assert_owner_eq!(context, "Foo", "Object");
2130
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
2131
+
2132
+ assert_members_eq!(context, "Foo::<Foo>", vec!["BAZ", "bar()"]);
2133
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
2134
+ }
2135
+
2136
+ #[test]
2137
+ fn resolution_for_nested_singleton_class() {
2138
+ let mut context = GraphTest::new();
2139
+ context.index_uri("file:///foo.rb", {
2140
+ r"
2141
+ class Foo
2142
+ class << self
2143
+ class << self
2144
+ def baz; end
2145
+ end
2146
+ end
2147
+ end
2148
+ "
2149
+ });
2150
+ context.resolve();
2151
+
2152
+ assert_no_diagnostics!(&context);
2153
+
2154
+ assert_no_members!(context, "Foo");
2155
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
2156
+
2157
+ assert_no_members!(context, "Foo::<Foo>");
2158
+ assert_singleton_class_eq!(context, "Foo::<Foo>", "Foo::<Foo>::<<Foo>>");
2159
+
2160
+ assert_members_eq!(context, "Foo::<Foo>::<<Foo>>", vec!["baz()"]);
2161
+ assert_owner_eq!(context, "Foo::<Foo>::<<Foo>>", "Foo::<Foo>");
2162
+ }
2163
+
2164
+ #[test]
2165
+ fn resolution_for_singleton_class_of_external_constant() {
2166
+ let mut context = GraphTest::new();
2167
+ context.index_uri("file:///foo.rb", {
2168
+ r"
2169
+ class Foo; end
2170
+ class Bar
2171
+ class << Foo
2172
+ def baz; end
2173
+
2174
+ class Baz; end
2175
+ end
2176
+ end
2177
+ "
2178
+ });
2179
+ context.resolve();
2180
+
2181
+ assert_no_diagnostics!(&context);
2182
+
2183
+ assert_no_members!(context, "Foo");
2184
+ assert_owner_eq!(context, "Foo", "Object");
2185
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
2186
+
2187
+ assert_no_members!(context, "Bar");
2188
+ assert_owner_eq!(context, "Bar", "Object");
2189
+
2190
+ assert_members_eq!(context, "Foo::<Foo>", vec!["Baz", "baz()"]);
2191
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
2192
+ }
2193
+
2194
+ #[test]
2195
+ fn resolution_for_class_variable_in_nested_singleton_class() {
2196
+ let mut context = GraphTest::new();
2197
+ context.index_uri("file:///foo.rb", {
2198
+ r"
2199
+ class Foo
2200
+ class << self
2201
+ @@bar = 123
2202
+
2203
+ class << self
2204
+ @@baz = 456
2205
+ end
2206
+ end
2207
+ end
2208
+ "
2209
+ });
2210
+ context.resolve();
2211
+
2212
+ assert_no_diagnostics!(&context);
2213
+
2214
+ assert_members_eq!(context, "Foo", vec!["@@bar", "@@baz"]);
2215
+ assert_owner_eq!(context, "Foo", "Object");
2216
+ }
2217
+
2218
+ #[test]
2219
+ fn resolution_for_class_variable_in_method() {
2220
+ let mut context = GraphTest::new();
2221
+ context.index_uri("file:///foo.rb", {
2222
+ r"
2223
+ class Foo
2224
+ def bar
2225
+ @@baz = 456
2226
+ end
2227
+ end
2228
+ "
2229
+ });
2230
+ context.resolve();
2231
+
2232
+ assert_no_diagnostics!(&context);
2233
+
2234
+ assert_members_eq!(context, "Foo", vec!["@@baz", "bar()"]);
2235
+ }
2236
+
2237
+ #[test]
2238
+ fn resolution_for_class_variable_only_follows_lexical_nesting() {
2239
+ let mut context = GraphTest::new();
2240
+ context.index_uri("file:///foo.rb", {
2241
+ r"
2242
+ class Foo; end
2243
+ class Bar
2244
+ def Foo.demo
2245
+ @@cvar1 = 1
2246
+ end
2247
+
2248
+ class << Foo
2249
+ def demo2
2250
+ @@cvar2 = 1
2251
+ end
2252
+ end
2253
+ end
2254
+ "
2255
+ });
2256
+ context.resolve();
2257
+
2258
+ assert_no_diagnostics!(&context);
2259
+
2260
+ assert_no_members!(context, "Foo");
2261
+ assert_members_eq!(context, "Bar", vec!["@@cvar1", "@@cvar2"]);
2262
+ }
2263
+
2264
+ #[test]
2265
+ fn resolution_for_class_variable_at_top_level() {
2266
+ let mut context = GraphTest::new();
2267
+ context.index_uri("file:///foo.rb", {
2268
+ "
2269
+ @@var = 123
2270
+ "
2271
+ });
2272
+ context.resolve();
2273
+
2274
+ assert_no_diagnostics!(&context);
2275
+
2276
+ // TODO: this should push an error diagnostic
2277
+ assert!(
2278
+ context
2279
+ .graph()
2280
+ .declarations()
2281
+ .get(&DeclarationId::from("Object::@@var"))
2282
+ .is_none()
2283
+ );
2284
+ }
2285
+
2286
+ #[test]
2287
+ fn singleton_class_is_set() {
2288
+ let mut context = GraphTest::new();
2289
+ context.index_uri("file:///foo.rb", {
2290
+ "
2291
+ class Foo
2292
+ class << self
2293
+ end
2294
+ end
2295
+ "
2296
+ });
2297
+
2298
+ context.resolve();
2299
+
2300
+ assert_no_diagnostics!(&context);
2301
+
2302
+ assert!(
2303
+ context
2304
+ .graph()
2305
+ .declarations()
2306
+ .get(&DeclarationId::from("Foo::<Foo>"))
2307
+ .is_some()
2308
+ );
2309
+
2310
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
2311
+ }
2312
+
2313
+ #[test]
2314
+ fn resolution_for_method_with_receiver() {
2315
+ let mut context = GraphTest::new();
2316
+ context.index_uri("file:///foo.rb", {
2317
+ r"
2318
+ class Foo
2319
+ def self.bar; end
2320
+
2321
+ class << self
2322
+ def self.nested_bar; end
2323
+ end
2324
+ end
2325
+
2326
+ class Bar
2327
+ def Foo.baz; end
2328
+
2329
+ def self.qux; end
2330
+ end
2331
+ "
2332
+ });
2333
+ context.resolve();
2334
+
2335
+ assert_no_diagnostics!(&context);
2336
+
2337
+ assert_members_eq!(context, "Foo::<Foo>", vec!["bar()", "baz()"]);
2338
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
2339
+
2340
+ assert_members_eq!(context, "Foo::<Foo>::<<Foo>>", vec!["nested_bar()"]);
2341
+ assert_owner_eq!(context, "Foo::<Foo>::<<Foo>>", "Foo::<Foo>");
2342
+
2343
+ assert_members_eq!(context, "Bar::<Bar>", vec!["qux()"]);
2344
+ assert_owner_eq!(context, "Bar::<Bar>", "Bar");
2345
+ }
2346
+
2347
+ #[test]
2348
+ fn linearizing_super_classes() {
2349
+ let mut context = GraphTest::new();
2350
+ context.index_uri("file:///foo.rb", {
2351
+ r"
2352
+ class Foo; end
2353
+ class Bar < Foo; end
2354
+ class Baz < Bar; end
2355
+ class Qux < Baz; end
2356
+ "
2357
+ });
2358
+ context.resolve();
2359
+
2360
+ assert_no_diagnostics!(&context);
2361
+
2362
+ assert_ancestors_eq!(context, "Qux", ["Qux", "Baz", "Bar", "Foo", "Object"]);
2363
+ }
2364
+
2365
+ #[test]
2366
+ fn descendants_are_tracked_for_parent_classes() {
2367
+ let mut context = GraphTest::new();
2368
+ context.index_uri("file:///foo.rb", {
2369
+ r"
2370
+ class Foo
2371
+ CONST = 123
2372
+ end
2373
+
2374
+ class Bar < Foo; end
2375
+
2376
+ class Baz < Bar
2377
+ CONST
2378
+ end
2379
+
2380
+ class Qux < Bar
2381
+ CONST
2382
+ end
2383
+ "
2384
+ });
2385
+ context.resolve();
2386
+
2387
+ assert_no_diagnostics!(&context);
2388
+
2389
+ assert_descendants!(context, "Foo", ["Bar"]);
2390
+ assert_descendants!(context, "Bar", ["Baz", "Qux"]);
2391
+ }
2392
+
2393
+ #[test]
2394
+ fn linearizing_circular_super_classes() {
2395
+ let mut context = GraphTest::new();
2396
+ context.index_uri("file:///foo.rb", {
2397
+ r"
2398
+ class Foo < Bar; end
2399
+ class Bar < Baz; end
2400
+ class Baz < Foo; end
2401
+ "
2402
+ });
2403
+ context.resolve();
2404
+
2405
+ assert_no_diagnostics!(&context);
2406
+
2407
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Bar", "Baz", "Object"]);
2408
+ }
2409
+
2410
+ #[test]
2411
+ fn resolving_a_constant_inherited_from_the_super_class() {
2412
+ let mut context = GraphTest::new();
2413
+ context.index_uri("file:///foo.rb", {
2414
+ r"
2415
+ class Foo
2416
+ CONST = 123
2417
+ end
2418
+
2419
+ class Bar < Foo
2420
+ CONST
2421
+ end
2422
+ "
2423
+ });
2424
+ context.resolve();
2425
+
2426
+ assert_no_diagnostics!(&context);
2427
+
2428
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:5:2-5:7");
2429
+ }
2430
+
2431
+ #[test]
2432
+ fn does_not_loop_forever_on_non_existing_parents() {
2433
+ let mut context = GraphTest::new();
2434
+ context.index_uri("file:///foo.rb", {
2435
+ r"
2436
+ class Bar < Foo
2437
+ CONST
2438
+ end
2439
+ "
2440
+ });
2441
+ context.resolve();
2442
+
2443
+ assert_no_diagnostics!(&context);
2444
+
2445
+ let declaration = context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap();
2446
+ assert!(matches!(
2447
+ declaration.as_namespace().unwrap().ancestors(),
2448
+ Ancestors::Partial(_)
2449
+ ));
2450
+ }
2451
+
2452
+ #[test]
2453
+ fn resolving_inherited_constant_dependent_on_complex_parent() {
2454
+ let mut context = GraphTest::new();
2455
+ context.index_uri("file:///foo.rb", {
2456
+ r"
2457
+ module Foo
2458
+ module Bar
2459
+ class Baz
2460
+ CONST = 123
2461
+ end
2462
+ end
2463
+ end
2464
+ class Qux < Foo::Bar::Baz
2465
+ CONST
2466
+ end
2467
+ "
2468
+ });
2469
+ context.resolve();
2470
+
2471
+ assert_no_diagnostics!(&context);
2472
+
2473
+ assert_constant_reference_to!(context, "Foo::Bar::Baz::CONST", "file:///foo.rb:8:2-8:7");
2474
+ }
2475
+
2476
+ #[test]
2477
+ fn resolution_for_instance_and_class_instance_variables() {
2478
+ let mut context = GraphTest::new();
2479
+ context.index_uri("file:///foo.rb", {
2480
+ r"
2481
+ class Foo
2482
+ @foo = 0
2483
+
2484
+ def initialize
2485
+ @bar = 1
2486
+ end
2487
+
2488
+ def self.baz
2489
+ @baz = 2
2490
+ end
2491
+
2492
+ class << self
2493
+ def qux
2494
+ @qux = 3
2495
+ end
2496
+
2497
+ def self.nested
2498
+ @nested = 4
2499
+ end
2500
+ end
2501
+ end
2502
+ "
2503
+ });
2504
+ context.resolve();
2505
+
2506
+ assert_no_diagnostics!(&context);
2507
+
2508
+ assert_instance_variables_eq!(context, "Foo", vec!["@bar"]);
2509
+ // @qux in `class << self; def qux` - self is Foo when called, so @qux belongs to Foo's singleton class
2510
+ assert_instance_variables_eq!(context, "Foo::<Foo>", vec!["@baz", "@foo", "@qux"]);
2511
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>", vec!["@nested"]);
2512
+ }
2513
+
2514
+ #[test]
2515
+ fn resolution_for_instance_variables_with_dynamic_method_owner() {
2516
+ let mut context = GraphTest::new();
2517
+ context.index_uri("file:///foo.rb", {
2518
+ r"
2519
+ class Foo
2520
+ end
2521
+
2522
+ class Bar
2523
+ def Foo.bar
2524
+ @foo = 0
2525
+ end
2526
+
2527
+ class << Foo
2528
+ def Bar.baz
2529
+ @baz = 1
2530
+ end
2531
+ end
2532
+ end
2533
+ "
2534
+ });
2535
+ context.resolve();
2536
+
2537
+ assert_no_diagnostics!(&context);
2538
+
2539
+ assert_instance_variables_eq!(context, "Foo::<Foo>", vec!["@foo"]);
2540
+ assert_instance_variables_eq!(context, "Bar::<Bar>", vec!["@baz"]);
2541
+ }
2542
+
2543
+ #[test]
2544
+ fn resolution_for_class_instance_variable_in_compact_namespace() {
2545
+ let mut context = GraphTest::new();
2546
+ context.index_uri("file:///foo.rb", {
2547
+ r"
2548
+ class Bar; end
2549
+
2550
+ class Foo
2551
+ class Bar::Baz
2552
+ @baz = 1
2553
+ end
2554
+ end
2555
+ "
2556
+ });
2557
+ context.resolve();
2558
+
2559
+ assert_no_diagnostics!(&context);
2560
+
2561
+ // The class is `Bar::Baz`, so its singleton class is `Bar::Baz::<Baz>`
2562
+ assert_instance_variables_eq!(context, "Bar::Baz::<Baz>", vec!["@baz"]);
2563
+ }
2564
+
2565
+ #[test]
2566
+ fn resolution_for_instance_variable_in_singleton_class_body() {
2567
+ let mut context = GraphTest::new();
2568
+ context.index_uri("file:///foo.rb", {
2569
+ r"
2570
+ class Foo
2571
+ class << self
2572
+ @bar = 1
2573
+
2574
+ class << self
2575
+ @baz = 2
2576
+ end
2577
+ end
2578
+ end
2579
+ "
2580
+ });
2581
+ context.resolve();
2582
+
2583
+ assert_no_diagnostics!(&context);
2584
+
2585
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>", vec!["@bar"]);
2586
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>::<<<Foo>>>", vec!["@baz"]);
2587
+ }
2588
+
2589
+ #[test]
2590
+ fn resolution_for_top_level_instance_variable() {
2591
+ let mut context = GraphTest::new();
2592
+ context.index_uri("file:///foo.rb", {
2593
+ r"
2594
+ @foo = 0
2595
+ "
2596
+ });
2597
+ context.resolve();
2598
+
2599
+ assert_no_diagnostics!(&context);
2600
+
2601
+ // Top-level instance variables belong to `<main>`, not `Object`.
2602
+ // We can't represent `<main>` yet, so no declaration is created.
2603
+ let foo_decl = context.graph().declarations().get(&DeclarationId::from("Object::@foo"));
2604
+ assert!(foo_decl.is_none(), "Object::@foo declaration should not exist");
2605
+ }
2606
+
2607
+ #[test]
2608
+ fn resolution_for_instance_variable_with_unresolved_receiver() {
2609
+ let mut context = GraphTest::new();
2610
+ context.index_uri("file:///foo.rb", {
2611
+ r"
2612
+ class Foo
2613
+ def foo.bar
2614
+ @baz = 0
2615
+ end
2616
+ end
2617
+ "
2618
+ });
2619
+ context.resolve();
2620
+
2621
+ assert_diagnostics_eq!(
2622
+ &context,
2623
+ vec!["dynamic-singleton-definition: Dynamic receiver for singleton method definition (2:3-4:6)",]
2624
+ );
2625
+
2626
+ // Instance variable in method with unresolved receiver should not create a declaration
2627
+ let baz_decl = context.graph().declarations().get(&DeclarationId::from("Object::@baz"));
2628
+ assert!(baz_decl.is_none(), "@baz declaration should not exist");
2629
+
2630
+ let foo_baz_decl = context.graph().declarations().get(&DeclarationId::from("Foo::@baz"));
2631
+ assert!(foo_baz_decl.is_none(), "Foo::@baz declaration should not exist");
2632
+ }
2633
+
2634
+ #[test]
2635
+ fn resolving_method_alias() {
2636
+ let mut context = GraphTest::new();
2637
+ context.index_uri("file:///foo.rb", {
2638
+ r"
2639
+ class Foo
2640
+ def foo; end
2641
+
2642
+ alias bar foo
2643
+ end
2644
+ "
2645
+ });
2646
+ context.resolve();
2647
+
2648
+ assert_no_diagnostics!(&context);
2649
+
2650
+ assert_members_eq!(context, "Foo", vec!["bar()", "foo()"]);
2651
+ }
2652
+
2653
+ #[test]
2654
+ fn resolving_global_variable_alias() {
2655
+ let mut context = GraphTest::new();
2656
+ context.index_uri("file:///foo.rb", {
2657
+ r"
2658
+ $foo = 123
2659
+ alias $bar $foo
2660
+ "
2661
+ });
2662
+ context.resolve();
2663
+
2664
+ assert_no_diagnostics!(&context);
2665
+
2666
+ assert_members_eq!(context, "Object", vec!["$bar", "$foo"]);
2667
+ }
2668
+
2669
+ #[test]
2670
+ fn linearizing_parent_classes_with_parent_scope() {
2671
+ let mut context = GraphTest::new();
2672
+ context.index_uri("file:///foo.rb", {
2673
+ r"
2674
+ module Foo
2675
+ class Bar
2676
+ end
2677
+ end
2678
+ class Baz < Foo::Bar
2679
+ end
2680
+ "
2681
+ });
2682
+ context.resolve();
2683
+
2684
+ assert_no_diagnostics!(&context);
2685
+
2686
+ assert_ancestors_eq!(context, "Baz", ["Baz", "Foo::Bar", "Object"]);
2687
+ }
2688
+
2689
+ #[test]
2690
+ fn resolving_constant_references_involved_in_prepends() {
2691
+ let mut context = GraphTest::new();
2692
+
2693
+ // To linearize the ancestors of `Bar`, we need to resolve `Foo` first. However, during that resolution, we need
2694
+ // to check `Bar`'s ancestor chain before checking the top level (which is where we'll find `Foo`). In these
2695
+ // scenarios, we need to realize the dependency and skip ancestors
2696
+ context.index_uri("file:///foo.rb", {
2697
+ r"
2698
+ module Foo; end
2699
+ module Bar
2700
+ prepend Foo
2701
+ end
2702
+ "
2703
+ });
2704
+ context.resolve();
2705
+
2706
+ assert_no_diagnostics!(&context);
2707
+
2708
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
2709
+ }
2710
+
2711
+ #[test]
2712
+ fn resolving_prepend_using_inherited_constant() {
2713
+ let mut context = GraphTest::new();
2714
+ // Prepending `Foo` makes `Bar` available, which we can then prepend as well. This requires resolving constants
2715
+ // with partially linearized ancestors
2716
+ context.index_uri("file:///foo.rb", {
2717
+ r"
2718
+ module Foo
2719
+ module Bar; end
2720
+ end
2721
+ class Baz
2722
+ prepend Foo
2723
+ prepend Bar
2724
+ end
2725
+ "
2726
+ });
2727
+ context.resolve();
2728
+
2729
+ assert_no_diagnostics!(&context);
2730
+
2731
+ assert_ancestors_eq!(context, "Baz", ["Foo::Bar", "Foo", "Baz", "Object"]);
2732
+ }
2733
+
2734
+ #[test]
2735
+ fn linearizing_prepended_modules() {
2736
+ let mut context = GraphTest::new();
2737
+ context.index_uri("file:///foo.rb", {
2738
+ r"
2739
+ module Foo; end
2740
+ module Bar
2741
+ prepend Foo
2742
+ end
2743
+ class Baz
2744
+ prepend Bar
2745
+ end
2746
+ class Qux < Baz; end
2747
+ "
2748
+ });
2749
+ context.resolve();
2750
+
2751
+ assert_no_diagnostics!(&context);
2752
+
2753
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
2754
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
2755
+ assert_ancestors_eq!(context, "Qux", ["Qux", "Foo", "Bar", "Baz", "Object"]);
2756
+ }
2757
+
2758
+ #[test]
2759
+ fn prepend_on_dynamic_namespace_definitions() {
2760
+ let mut context = GraphTest::new();
2761
+ context.index_uri("file:///foo.rb", {
2762
+ r"
2763
+ module B; end
2764
+ A = Struct.new do
2765
+ prepend B
2766
+ end
2767
+
2768
+ C = Class.new do
2769
+ prepend B
2770
+ end
2771
+
2772
+ D = Module.new do
2773
+ prepend B
2774
+ end
2775
+ "
2776
+ });
2777
+ context.resolve();
2778
+
2779
+ assert_no_diagnostics!(&context);
2780
+
2781
+ assert_ancestors_eq!(context, "B", ["B"]);
2782
+ // TODO: this is a temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
2783
+ //assert_ancestors_eq!(context, "A", Vec::<&str>::new());
2784
+ assert_ancestors_eq!(context, "C", ["B", "C", "Object"]);
2785
+ assert_ancestors_eq!(context, "D", ["B", "D"]);
2786
+ }
2787
+
2788
+ #[test]
2789
+ fn prepends_track_descendants() {
2790
+ let mut context = GraphTest::new();
2791
+ context.index_uri("file:///foo.rb", {
2792
+ r"
2793
+ module Foo; end
2794
+ module Bar
2795
+ prepend Foo
2796
+ end
2797
+ class Baz
2798
+ prepend Bar
2799
+ end
2800
+ "
2801
+ });
2802
+ context.resolve();
2803
+
2804
+ assert_no_diagnostics!(&context);
2805
+
2806
+ assert_descendants!(context, "Foo", ["Bar", "Baz"]);
2807
+ assert_descendants!(context, "Bar", ["Baz"]);
2808
+ }
2809
+
2810
+ #[test]
2811
+ fn cyclic_prepend() {
2812
+ let mut context = GraphTest::new();
2813
+ context.index_uri("file:///foo.rb", {
2814
+ r"
2815
+ module Foo
2816
+ prepend Foo
2817
+ end
2818
+ "
2819
+ });
2820
+ context.resolve();
2821
+
2822
+ assert_no_diagnostics!(&context);
2823
+
2824
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
2825
+ }
2826
+
2827
+ #[test]
2828
+ fn duplicate_prepends() {
2829
+ let mut context = GraphTest::new();
2830
+ context.index_uri("file:///foo.rb", {
2831
+ r"
2832
+ module Foo
2833
+ end
2834
+
2835
+ module Bar
2836
+ prepend Foo
2837
+ prepend Foo
2838
+ end
2839
+ "
2840
+ });
2841
+ context.resolve();
2842
+
2843
+ assert_no_diagnostics!(&context);
2844
+
2845
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
2846
+ }
2847
+
2848
+ #[test]
2849
+ fn indirect_duplicate_prepends() {
2850
+ let mut context = GraphTest::new();
2851
+ context.index_uri("file:///foo.rb", {
2852
+ r"
2853
+ module A; end
2854
+
2855
+ module B
2856
+ prepend A
2857
+ end
2858
+
2859
+ module C
2860
+ prepend A
2861
+ end
2862
+
2863
+ module Foo
2864
+ prepend B
2865
+ prepend C
2866
+ end
2867
+ "
2868
+ });
2869
+ context.resolve();
2870
+
2871
+ assert_no_diagnostics!(&context);
2872
+
2873
+ assert_ancestors_eq!(context, "A", ["A"]);
2874
+ assert_ancestors_eq!(context, "B", ["A", "B"]);
2875
+ assert_ancestors_eq!(context, "C", ["A", "C"]);
2876
+ assert_ancestors_eq!(context, "Foo", ["A", "C", "B", "Foo"]);
2877
+ }
2878
+
2879
+ #[test]
2880
+ fn multiple_mixins_in_same_prepend() {
2881
+ let mut context = GraphTest::new();
2882
+ context.index_uri("file:///foo.rb", {
2883
+ r"
2884
+ module A; end
2885
+ module B; end
2886
+
2887
+ class Foo
2888
+ prepend A, B
2889
+ end
2890
+ "
2891
+ });
2892
+ context.resolve();
2893
+
2894
+ assert_no_diagnostics!(&context);
2895
+
2896
+ assert_ancestors_eq!(context, "Foo", ["A", "B", "Foo", "Object"]);
2897
+ }
2898
+
2899
+ #[test]
2900
+ fn prepends_involving_parent_scopes() {
2901
+ let mut context = GraphTest::new();
2902
+ context.index_uri("file:///foo.rb", {
2903
+ r"
2904
+ module A
2905
+ module B
2906
+ module C; end
2907
+ end
2908
+ end
2909
+
2910
+ module D
2911
+ prepend A::B::C
2912
+ end
2913
+
2914
+ module Foo
2915
+ prepend D
2916
+ prepend A::B::C
2917
+ end
2918
+
2919
+ module Bar
2920
+ prepend A::B::C
2921
+ prepend D
2922
+ end
2923
+ "
2924
+ });
2925
+ context.resolve();
2926
+
2927
+ assert_no_diagnostics!(&context);
2928
+
2929
+ assert_ancestors_eq!(context, "Foo", ["A::B::C", "D", "Foo"]);
2930
+ assert_ancestors_eq!(context, "Bar", ["A::B::C", "D", "Bar"]);
2931
+ }
2932
+
2933
+ #[test]
2934
+ fn duplicate_prepends_in_parents() {
2935
+ let mut context = GraphTest::new();
2936
+ context.index_uri("file:///foo.rb", {
2937
+ r"
2938
+ module A; end
2939
+
2940
+ module B
2941
+ prepend A
2942
+ end
2943
+
2944
+ class Parent
2945
+ prepend B
2946
+ end
2947
+
2948
+ class Child < Parent
2949
+ prepend B
2950
+ end
2951
+ "
2952
+ });
2953
+ context.resolve();
2954
+
2955
+ assert_no_diagnostics!(&context);
2956
+
2957
+ assert_ancestors_eq!(context, "Child", ["A", "B", "Child", "A", "B", "Parent", "Object"]);
2958
+ }
2959
+
2960
+ #[test]
2961
+ fn prepended_modules_involved_in_definitions() {
2962
+ let mut context = GraphTest::new();
2963
+ context.index_uri("file:///foo.rb", {
2964
+ r"
2965
+ module Foo
2966
+ module Bar; end
2967
+ end
2968
+
2969
+ module Baz
2970
+ prepend Foo
2971
+
2972
+ class Bar::Qux
2973
+ end
2974
+ end
2975
+ "
2976
+ });
2977
+ context.resolve();
2978
+
2979
+ assert_no_diagnostics!(&context);
2980
+
2981
+ assert_members_eq!(context, "Foo::Bar", vec!["Qux"]);
2982
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
2983
+
2984
+ assert_no_members!(context, "Foo::Bar::Qux");
2985
+ assert_owner_eq!(context, "Foo::Bar::Qux", "Foo::Bar");
2986
+ }
2987
+
2988
+ #[test]
2989
+ fn resolving_constant_references_involved_in_includes() {
2990
+ let mut context = GraphTest::new();
2991
+ context.index_uri("file:///foo.rb", {
2992
+ r"
2993
+ module Foo; end
2994
+ module Bar
2995
+ include Foo
2996
+ end
2997
+ "
2998
+ });
2999
+ context.resolve();
3000
+
3001
+ assert_no_diagnostics!(&context);
3002
+
3003
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Foo"]);
3004
+ }
3005
+
3006
+ #[test]
3007
+ fn resolving_include_using_inherited_constant() {
3008
+ let mut context = GraphTest::new();
3009
+ context.index_uri("file:///foo.rb", {
3010
+ r"
3011
+ module Foo
3012
+ module Bar; end
3013
+ end
3014
+ class Baz
3015
+ include Foo
3016
+ include Bar
3017
+ end
3018
+ "
3019
+ });
3020
+ context.resolve();
3021
+
3022
+ assert_no_diagnostics!(&context);
3023
+
3024
+ assert_ancestors_eq!(context, "Baz", ["Baz", "Foo::Bar", "Foo", "Object"]);
3025
+ }
3026
+
3027
+ #[test]
3028
+ fn linearizing_included_modules() {
3029
+ let mut context = GraphTest::new();
3030
+ context.index_uri("file:///foo.rb", {
3031
+ r"
3032
+ module Foo; end
3033
+ module Bar
3034
+ prepend Foo
3035
+ end
3036
+ class Baz
3037
+ prepend Bar
3038
+ end
3039
+ class Qux < Baz; end
3040
+ "
3041
+ });
3042
+ context.resolve();
3043
+
3044
+ assert_no_diagnostics!(&context);
3045
+
3046
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
3047
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
3048
+ assert_ancestors_eq!(context, "Qux", ["Qux", "Foo", "Bar", "Baz", "Object"]);
3049
+ }
3050
+
3051
+ #[test]
3052
+ fn include_on_dynamic_namespace_definitions() {
3053
+ let mut context = GraphTest::new();
3054
+ context.index_uri("file:///foo.rb", {
3055
+ r"
3056
+ module B; end
3057
+ A = Struct.new do
3058
+ include B
3059
+ end
3060
+
3061
+ C = Class.new do
3062
+ include B
3063
+ end
3064
+
3065
+ D = Module.new do
3066
+ include B
3067
+ end
3068
+ "
3069
+ });
3070
+ context.resolve();
3071
+
3072
+ assert_no_diagnostics!(&context);
3073
+
3074
+ assert_ancestors_eq!(context, "B", ["B"]);
3075
+ // TODO: this is a temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
3076
+ //assert_ancestors_eq!(context, "A", Vec::<&str>::new());
3077
+ assert_ancestors_eq!(context, "C", ["C", "B", "Object"]);
3078
+ assert_ancestors_eq!(context, "D", ["D", "B"]);
3079
+ }
3080
+
3081
+ #[test]
3082
+ fn cyclic_include() {
3083
+ let mut context = GraphTest::new();
3084
+ context.index_uri("file:///foo.rb", {
3085
+ r"
3086
+ module Foo
3087
+ include Foo
3088
+ end
3089
+ "
3090
+ });
3091
+ context.resolve();
3092
+
3093
+ assert_no_diagnostics!(&context);
3094
+
3095
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
3096
+ }
3097
+
3098
+ #[test]
3099
+ fn duplicate_includes() {
3100
+ let mut context = GraphTest::new();
3101
+ context.index_uri("file:///foo.rb", {
3102
+ r"
3103
+ module Foo
3104
+ end
3105
+
3106
+ module Bar
3107
+ include Foo
3108
+ include Foo
3109
+ end
3110
+ "
3111
+ });
3112
+ context.resolve();
3113
+
3114
+ assert_no_diagnostics!(&context);
3115
+
3116
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Foo"]);
3117
+ }
3118
+
3119
+ #[test]
3120
+ fn indirect_duplicate_includes() {
3121
+ let mut context = GraphTest::new();
3122
+ context.index_uri("file:///foo.rb", {
3123
+ r"
3124
+ module A; end
3125
+
3126
+ module B
3127
+ include A
3128
+ end
3129
+
3130
+ module C
3131
+ include A
3132
+ end
3133
+
3134
+ module Foo
3135
+ include B
3136
+ include C
3137
+ end
3138
+ "
3139
+ });
3140
+ context.resolve();
3141
+
3142
+ assert_no_diagnostics!(&context);
3143
+
3144
+ assert_ancestors_eq!(context, "A", ["A"]);
3145
+ assert_ancestors_eq!(context, "B", ["B", "A"]);
3146
+ assert_ancestors_eq!(context, "C", ["C", "A"]);
3147
+ assert_ancestors_eq!(context, "Foo", ["Foo", "C", "B", "A"]);
3148
+ }
3149
+
3150
+ #[test]
3151
+ fn includes_involving_parent_scopes() {
3152
+ let mut context = GraphTest::new();
3153
+ context.index_uri("file:///foo.rb", {
3154
+ r"
3155
+ module A
3156
+ module B
3157
+ module C; end
3158
+ end
3159
+ end
3160
+
3161
+ module D
3162
+ include A::B::C
3163
+ end
3164
+
3165
+ module Foo
3166
+ include D
3167
+ include A::B::C
3168
+ end
3169
+
3170
+ module Bar
3171
+ include A::B::C
3172
+ include D
3173
+ end
3174
+ "
3175
+ });
3176
+ context.resolve();
3177
+
3178
+ assert_no_diagnostics!(&context);
3179
+
3180
+ assert_ancestors_eq!(context, "Foo", ["Foo", "D", "A::B::C"]);
3181
+ assert_ancestors_eq!(context, "Bar", ["Bar", "D", "A::B::C"]);
3182
+ }
3183
+
3184
+ #[test]
3185
+ fn duplicate_includes_in_parents() {
3186
+ let mut context = GraphTest::new();
3187
+ context.index_uri("file:///foo.rb", {
3188
+ r"
3189
+ module A; end
3190
+
3191
+ module B
3192
+ include A
3193
+ end
3194
+
3195
+ class Parent
3196
+ include B
3197
+ end
3198
+
3199
+ class Child < Parent
3200
+ include B
3201
+ end
3202
+ "
3203
+ });
3204
+ context.resolve();
3205
+
3206
+ assert_no_diagnostics!(&context);
3207
+
3208
+ assert_ancestors_eq!(context, "Child", ["Child", "Parent", "B", "A", "Object"]);
3209
+ }
3210
+
3211
+ #[test]
3212
+ fn included_modules_involved_in_definitions() {
3213
+ let mut context = GraphTest::new();
3214
+ context.index_uri("file:///foo.rb", {
3215
+ r"
3216
+ module Foo
3217
+ module Bar; end
3218
+ end
3219
+
3220
+ module Baz
3221
+ include Foo
3222
+
3223
+ class Bar::Qux
3224
+ end
3225
+ end
3226
+ "
3227
+ });
3228
+ context.resolve();
3229
+
3230
+ assert_no_diagnostics!(&context);
3231
+
3232
+ assert_members_eq!(context, "Foo::Bar", vec!["Qux"]);
3233
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
3234
+
3235
+ assert_no_members!(context, "Foo::Bar::Qux");
3236
+ assert_owner_eq!(context, "Foo::Bar::Qux", "Foo::Bar");
3237
+ }
3238
+
3239
+ #[test]
3240
+ fn references_with_parent_scope_search_inheritance() {
3241
+ let mut context = GraphTest::new();
3242
+ context.index_uri("file:///foo.rb", {
3243
+ r"
3244
+ module Foo
3245
+ module Bar; end
3246
+ end
3247
+
3248
+ class Baz
3249
+ include Foo
3250
+ end
3251
+
3252
+ Baz::Bar
3253
+ "
3254
+ });
3255
+ context.resolve();
3256
+
3257
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3258
+
3259
+ assert_constant_reference_to!(context, "Foo::Bar", "file:///foo.rb:8:5-8:8");
3260
+ }
3261
+
3262
+ #[test]
3263
+ fn duplicate_includes_and_prepends() {
3264
+ let mut context = GraphTest::new();
3265
+ context.index_uri("file:///foo.rb", {
3266
+ r"
3267
+ module A; end
3268
+
3269
+ class Foo
3270
+ prepend A
3271
+ include A
3272
+ end
3273
+
3274
+ class Bar
3275
+ include A
3276
+ prepend A
3277
+ end
3278
+ "
3279
+ });
3280
+ context.resolve();
3281
+
3282
+ assert_no_diagnostics!(&context);
3283
+
3284
+ assert_ancestors_eq!(context, "Foo", ["A", "Foo", "Object"]);
3285
+ assert_ancestors_eq!(context, "Bar", ["A", "Bar", "A", "Object"]);
3286
+ }
3287
+
3288
+ #[test]
3289
+ fn duplicate_indirect_includes_and_prepends() {
3290
+ let mut context = GraphTest::new();
3291
+ context.index_uri("file:///foo.rb", {
3292
+ r"
3293
+ module A; end
3294
+ module B
3295
+ include A
3296
+ end
3297
+ module C
3298
+ prepend A
3299
+ end
3300
+
3301
+ class Foo
3302
+ include C
3303
+ prepend B
3304
+ include A
3305
+ end
3306
+
3307
+ class Bar
3308
+ include A
3309
+ prepend B
3310
+ include C
3311
+ end
3312
+
3313
+ class Baz
3314
+ prepend B
3315
+ include C
3316
+ prepend A
3317
+ end
3318
+
3319
+ class Qux
3320
+ prepend A
3321
+ include C
3322
+ prepend B
3323
+ end
3324
+ "
3325
+ });
3326
+ context.resolve();
3327
+
3328
+ assert_no_diagnostics!(&context);
3329
+
3330
+ assert_ancestors_eq!(context, "Foo", ["B", "A", "Foo", "A", "C", "Object"]);
3331
+ assert_ancestors_eq!(context, "Bar", ["B", "A", "Bar", "C", "A", "Object"]);
3332
+ assert_ancestors_eq!(context, "Baz", ["B", "A", "Baz", "C", "Object"]);
3333
+ assert_ancestors_eq!(context, "Qux", ["B", "A", "Qux", "C", "Object"]);
3334
+ }
3335
+
3336
+ #[test]
3337
+ fn duplicate_includes_and_prepends_through_parents() {
3338
+ let mut context = GraphTest::new();
3339
+ context.index_uri("file:///foo.rb", {
3340
+ r"
3341
+ module A; end
3342
+
3343
+ class Parent
3344
+ include A
3345
+ end
3346
+
3347
+ class Foo < Parent
3348
+ prepend A
3349
+ end
3350
+
3351
+ class Bar < Parent
3352
+ include A
3353
+ end
3354
+ "
3355
+ });
3356
+ context.resolve();
3357
+
3358
+ assert_no_diagnostics!(&context);
3359
+
3360
+ assert_ancestors_eq!(context, "Foo", ["A", "Foo", "Parent", "A", "Object"]);
3361
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Parent", "A", "Object"]);
3362
+ }
3363
+
3364
+ #[test]
3365
+ fn multiple_mixins_in_same_include() {
3366
+ let mut context = GraphTest::new();
3367
+ context.index_uri("file:///foo.rb", {
3368
+ r"
3369
+ module A; end
3370
+ module B; end
3371
+
3372
+ class Foo
3373
+ include A, B
3374
+ end
3375
+ "
3376
+ });
3377
+ context.resolve();
3378
+
3379
+ assert_no_diagnostics!(&context);
3380
+
3381
+ assert_ancestors_eq!(context, "Foo", ["Foo", "A", "B", "Object"]);
3382
+ }
3383
+
3384
+ #[test]
3385
+ fn descendants_are_tracked_for_includes() {
3386
+ let mut context = GraphTest::new();
3387
+ context.index_uri("file:///foo.rb", {
3388
+ r"
3389
+ module Foo; end
3390
+ module Bar
3391
+ include Foo
3392
+ end
3393
+ module Baz
3394
+ include Bar
3395
+ end
3396
+ "
3397
+ });
3398
+ context.resolve();
3399
+
3400
+ assert_no_diagnostics!(&context);
3401
+
3402
+ assert_descendants!(context, "Bar", ["Baz"]);
3403
+ assert_descendants!(context, "Foo", ["Bar", "Baz"]);
3404
+ }
3405
+
3406
+ #[test]
3407
+ fn singleton_ancestors_for_classes() {
3408
+ let mut context = GraphTest::new();
3409
+ context.index_uri("file:///foo.rb", {
3410
+ r"
3411
+ module Foo; end
3412
+ module Qux; end
3413
+ module Zip; end
3414
+ class Bar; end
3415
+
3416
+ class Baz < Bar
3417
+ extend Foo
3418
+
3419
+ class << self
3420
+ include Qux
3421
+
3422
+ class << self
3423
+ include Zip
3424
+ end
3425
+ end
3426
+ end
3427
+ "
3428
+ });
3429
+ context.resolve();
3430
+
3431
+ assert_no_diagnostics!(&context);
3432
+
3433
+ // Note: the commented out parts require RBS indexing
3434
+ assert_ancestors_eq!(
3435
+ context,
3436
+ "Baz::<Baz>",
3437
+ [
3438
+ "Baz::<Baz>",
3439
+ "Qux",
3440
+ "Foo",
3441
+ "Bar::<Bar>",
3442
+ "Object::<Object>",
3443
+ // "BasicObject::<BasicObject>",
3444
+ "Class",
3445
+ // "Module",
3446
+ "Object",
3447
+ // "Kernel",
3448
+ // "BasicObject"
3449
+ ]
3450
+ );
3451
+
3452
+ assert_ancestors_eq!(
3453
+ context,
3454
+ "Baz::<Baz>::<<Baz>>",
3455
+ [
3456
+ "Baz::<Baz>::<<Baz>>",
3457
+ "Zip",
3458
+ "Bar::<Bar>::<<Bar>>",
3459
+ "Object::<Object>::<<Object>>",
3460
+ // "BasicObject::<BasicObject>::<<BasicObject>>",
3461
+ "Class::<Class>",
3462
+ // "Module::<Module>",
3463
+ "Object::<Object>",
3464
+ // "BasicObject::<BasicObject>",
3465
+ "Class",
3466
+ // "Module",
3467
+ "Object",
3468
+ // "Kernel",
3469
+ // "BasicObject"
3470
+ ]
3471
+ );
3472
+ }
3473
+
3474
+ #[test]
3475
+ fn singleton_ancestors_for_modules() {
3476
+ let mut context = GraphTest::new();
3477
+ context.index_uri("file:///foo.rb", {
3478
+ r"
3479
+ module Foo; end
3480
+ module Qux; end
3481
+ module Zip; end
3482
+ class Bar; end
3483
+
3484
+ module Baz
3485
+ extend Foo
3486
+
3487
+ class << self
3488
+ include Qux
3489
+
3490
+ class << self
3491
+ include Zip
3492
+ end
3493
+ end
3494
+ end
3495
+ "
3496
+ });
3497
+ context.resolve();
3498
+
3499
+ assert_no_diagnostics!(&context);
3500
+
3501
+ // Note: the commented out parts require RBS indexing
3502
+ assert_ancestors_eq!(
3503
+ context,
3504
+ "Baz::<Baz>",
3505
+ [
3506
+ "Baz::<Baz>",
3507
+ "Qux",
3508
+ "Foo",
3509
+ "Module",
3510
+ "Object",
3511
+ // "Kernel",
3512
+ // "BasicObject"
3513
+ ]
3514
+ );
3515
+ assert_ancestors_eq!(
3516
+ context,
3517
+ "Baz::<Baz>::<<Baz>>",
3518
+ [
3519
+ "Baz::<Baz>::<<Baz>>",
3520
+ "Zip",
3521
+ "Module::<Module>",
3522
+ "Object::<Object>",
3523
+ // "BasicObject::<BasicObject>",
3524
+ "Class",
3525
+ // "Module",
3526
+ "Object",
3527
+ // "Kernel",
3528
+ // "BasicObject"
3529
+ ]
3530
+ );
3531
+ }
3532
+
3533
+ #[test]
3534
+ fn singleton_ancestors_with_inherited_parent_modules() {
3535
+ let mut context = GraphTest::new();
3536
+ context.index_uri("file:///foo.rb", {
3537
+ r"
3538
+ module Foo; end
3539
+ module Qux; end
3540
+ class Bar
3541
+ class << self
3542
+ include Foo
3543
+ prepend Qux
3544
+ end
3545
+ end
3546
+
3547
+ class Baz < Bar
3548
+ class << self
3549
+ class << self
3550
+ end
3551
+ end
3552
+ end
3553
+ "
3554
+ });
3555
+ context.resolve();
3556
+
3557
+ assert_no_diagnostics!(&context);
3558
+
3559
+ // TODO: the commented out parts require RBS indexing
3560
+ assert_ancestors_eq!(
3561
+ context,
3562
+ "Bar::<Bar>",
3563
+ [
3564
+ "Qux",
3565
+ "Bar::<Bar>",
3566
+ "Foo",
3567
+ "Object::<Object>",
3568
+ // "BasicObject::<BasicObject>",
3569
+ "Class",
3570
+ // "Module",
3571
+ "Object",
3572
+ // "Kernel",
3573
+ // "BasicObject"
3574
+ ]
3575
+ );
3576
+
3577
+ assert_ancestors_eq!(
3578
+ context,
3579
+ "Baz::<Baz>",
3580
+ [
3581
+ "Baz::<Baz>",
3582
+ "Qux",
3583
+ "Bar::<Bar>",
3584
+ "Foo",
3585
+ "Object::<Object>",
3586
+ // "BasicObject::<BasicObject>",
3587
+ "Class",
3588
+ // "Module",
3589
+ "Object",
3590
+ // "Kernel",
3591
+ // "BasicObject"
3592
+ ]
3593
+ );
3594
+ assert_ancestors_eq!(
3595
+ context,
3596
+ "Baz::<Baz>::<<Baz>>",
3597
+ [
3598
+ "Baz::<Baz>::<<Baz>>",
3599
+ "Bar::<Bar>::<<Bar>>",
3600
+ "Object::<Object>::<<Object>>",
3601
+ // "BasicObject::<BasicObject>::<<BasicObject>>",
3602
+ "Class::<Class>",
3603
+ // "Module::<Module>",
3604
+ "Object::<Object>",
3605
+ // "BasicObject::<BasicObject>",
3606
+ "Class",
3607
+ // "Module",
3608
+ "Object",
3609
+ // "Kernel",
3610
+ // "BasicObject"
3611
+ ]
3612
+ );
3613
+ }
3614
+
3615
+ #[test]
3616
+ fn resolving_global_variable_alias_inside_method() {
3617
+ let mut context = GraphTest::new();
3618
+ context.index_uri("file:///foo.rb", {
3619
+ r"
3620
+ class Foo
3621
+ def setup
3622
+ alias $bar $baz
3623
+ end
3624
+ end
3625
+ "
3626
+ });
3627
+ context.resolve();
3628
+
3629
+ assert_no_diagnostics!(&context);
3630
+
3631
+ // Global variable aliases should still be owned by Object, regardless of where defined
3632
+ assert_members_eq!(context, "Object", vec!["$bar", "Foo"]);
3633
+ }
3634
+
3635
+ #[test]
3636
+ fn resolving_method_defined_inside_method() {
3637
+ let mut context = GraphTest::new();
3638
+ context.index_uri("file:///foo.rb", {
3639
+ r"
3640
+ class Foo
3641
+ def setup
3642
+ def inner_method; end
3643
+ end
3644
+ end
3645
+ "
3646
+ });
3647
+ context.resolve();
3648
+
3649
+ assert_no_diagnostics!(&context);
3650
+
3651
+ // inner_method should be owned by Foo, not by setup
3652
+ assert_members_eq!(context, "Foo", vec!["inner_method()", "setup()"]);
3653
+ }
3654
+
3655
+ #[test]
3656
+ fn resolving_attr_accessors_inside_method() {
3657
+ let mut context = GraphTest::new();
3658
+ context.index_uri("file:///foo.rb", {
3659
+ r"
3660
+ class Foo
3661
+ def self.setup
3662
+ attr_reader :reader_attr
3663
+ attr_writer :writer_attr
3664
+ attr_accessor :accessor_attr
3665
+ end
3666
+ end
3667
+ "
3668
+ });
3669
+ context.resolve();
3670
+
3671
+ assert_no_diagnostics!(&context);
3672
+
3673
+ assert_members_eq!(context, "Foo::<Foo>", vec!["setup()"]);
3674
+
3675
+ // All attr_* should be owned by Foo, not by setup
3676
+ assert_members_eq!(
3677
+ context,
3678
+ "Foo",
3679
+ vec!["accessor_attr()", "reader_attr()", "writer_attr()"]
3680
+ );
3681
+ }
3682
+
3683
+ #[test]
3684
+ fn resolving_constant_alias_to_module() {
3685
+ let mut context = GraphTest::new();
3686
+ context.index_uri("file:///foo.rb", {
3687
+ r"
3688
+ module Foo
3689
+ CONST = 123
3690
+ end
3691
+
3692
+ ALIAS = Foo
3693
+ ALIAS::CONST
3694
+ "
3695
+ });
3696
+ context.resolve();
3697
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3698
+
3699
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo");
3700
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:5:7-5:12");
3701
+ }
3702
+
3703
+ #[test]
3704
+ fn resolving_constant_alias_to_nested_module() {
3705
+ let mut context = GraphTest::new();
3706
+ context.index_uri("file:///foo.rb", {
3707
+ r"
3708
+ module Foo
3709
+ module Bar
3710
+ CONST = 123
3711
+ end
3712
+ end
3713
+
3714
+ ALIAS = Foo::Bar
3715
+ ALIAS::CONST
3716
+ "
3717
+ });
3718
+ context.resolve();
3719
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3720
+
3721
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo::Bar");
3722
+ assert_constant_reference_to!(context, "Foo::Bar::CONST", "file:///foo.rb:7:7-7:12");
3723
+ }
3724
+
3725
+ #[test]
3726
+ fn resolving_constant_alias_inside_module() {
3727
+ let mut context = GraphTest::new();
3728
+ context.index_uri("file:///foo.rb", {
3729
+ r"
3730
+ module Foo
3731
+ CONST = 123
3732
+ end
3733
+
3734
+ module Bar
3735
+ MyFoo = Foo
3736
+ MyFoo::CONST
3737
+ end
3738
+ "
3739
+ });
3740
+ context.resolve();
3741
+ assert_no_diagnostics!(&context);
3742
+
3743
+ assert_constant_alias_target_eq!(context, "Bar::MyFoo", "Foo");
3744
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:6:9-6:14");
3745
+ }
3746
+
3747
+ #[test]
3748
+ fn resolving_constant_alias_in_superclass() {
3749
+ let mut context = GraphTest::new();
3750
+ context.index_uri("file:///foo.rb", {
3751
+ r"
3752
+ class Foo
3753
+ CONST = 123
3754
+ end
3755
+
3756
+ class Bar < Foo
3757
+ end
3758
+
3759
+ ALIAS = Bar
3760
+ ALIAS::CONST
3761
+ "
3762
+ });
3763
+ context.resolve();
3764
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3765
+
3766
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:8:7-8:12");
3767
+ }
3768
+
3769
+ #[test]
3770
+ fn resolving_chained_constant_aliases() {
3771
+ let mut context = GraphTest::new();
3772
+ context.index_uri("file:///foo.rb", {
3773
+ r"
3774
+ module Foo
3775
+ CONST = 123
3776
+ end
3777
+
3778
+ ALIAS1 = Foo
3779
+ ALIAS2 = ALIAS1
3780
+ ALIAS2::CONST
3781
+ "
3782
+ });
3783
+ context.resolve();
3784
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3785
+
3786
+ assert_constant_alias_target_eq!(context, "ALIAS1", "Foo");
3787
+ assert_constant_alias_target_eq!(context, "ALIAS2", "ALIAS1");
3788
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:6:8-6:13");
3789
+ }
3790
+
3791
+ #[test]
3792
+ fn resolving_constant_alias_to_non_existent_target() {
3793
+ let mut context = GraphTest::new();
3794
+ context.index_uri("file:///foo.rb", {
3795
+ r"
3796
+ ALIAS_1 = NonExistent
3797
+ ALIAS_2 = ALIAS_1
3798
+ "
3799
+ });
3800
+ context.resolve();
3801
+ assert_no_diagnostics!(&context);
3802
+
3803
+ assert_constant_alias_target_eq!(context, "ALIAS_2", "ALIAS_1");
3804
+ assert_no_constant_alias_target!(context, "ALIAS_1");
3805
+ }
3806
+
3807
+ #[test]
3808
+ fn resolving_constant_alias_to_value_in_constant_path() {
3809
+ let mut context = GraphTest::new();
3810
+ context.index_uri("file:///foo.rb", {
3811
+ r"
3812
+ VALUE = 1
3813
+ ALIAS = VALUE
3814
+ ALIAS::NOPE
3815
+ "
3816
+ });
3817
+ context.resolve();
3818
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3819
+
3820
+ assert_constant_alias_target_eq!(context, "ALIAS", "VALUE");
3821
+
3822
+ // NOPE can't be created because ALIAS points to a value constant, not a namespace
3823
+ assert!(
3824
+ !context
3825
+ .graph()
3826
+ .declarations()
3827
+ .contains_key(&DeclarationId::from("VALUE::NOPE"))
3828
+ );
3829
+ }
3830
+
3831
+ #[test]
3832
+ fn resolving_constant_alias_defined_before_target() {
3833
+ let mut context = GraphTest::new();
3834
+ context.index_uri("file:///foo.rb", {
3835
+ r"
3836
+ ALIAS = Foo
3837
+ module Foo
3838
+ CONST = 1
3839
+ end
3840
+ ALIAS::CONST
3841
+ "
3842
+ });
3843
+ context.resolve();
3844
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3845
+
3846
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo");
3847
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:4:7-4:12");
3848
+ }
3849
+
3850
+ #[test]
3851
+ fn resolving_constant_alias_to_value() {
3852
+ let mut context = GraphTest::new();
3853
+ context.index_uri("file:///foo.rb", {
3854
+ r"
3855
+ class Foo
3856
+ CONST = 1
3857
+ end
3858
+ class Bar
3859
+ CONST = Foo::CONST
3860
+ end
3861
+ BAZ = Bar::CONST
3862
+ "
3863
+ });
3864
+ context.resolve();
3865
+ assert_no_diagnostics!(&context);
3866
+
3867
+ assert_constant_alias_target_eq!(context, "BAZ", "Bar::CONST");
3868
+ assert_constant_alias_target_eq!(context, "Bar::CONST", "Foo::CONST");
3869
+ }
3870
+
3871
+ #[test]
3872
+ fn resolving_circular_constant_aliases() {
3873
+ let mut context = GraphTest::new();
3874
+ context.index_uri("file:///foo.rb", {
3875
+ r"
3876
+ A = B
3877
+ B = C
3878
+ C = A
3879
+ "
3880
+ });
3881
+ context.resolve();
3882
+ assert_no_diagnostics!(&context);
3883
+
3884
+ assert_constant_alias_target_eq!(context, "A", "B");
3885
+ assert_constant_alias_target_eq!(context, "B", "C");
3886
+ assert_constant_alias_target_eq!(context, "C", "A");
3887
+ }
3888
+
3889
+ #[test]
3890
+ fn resolving_circular_constant_aliases_cross_namespace() {
3891
+ let mut context = GraphTest::new();
3892
+ context.index_uri("file:///foo.rb", {
3893
+ r"
3894
+ module A
3895
+ X = B::Y
3896
+ end
3897
+ module B
3898
+ Y = A::X
3899
+ end
3900
+
3901
+ A::X::SOMETHING = 1
3902
+ "
3903
+ });
3904
+ context.resolve();
3905
+ assert_no_diagnostics!(&context);
3906
+
3907
+ assert!(
3908
+ context
3909
+ .graph()
3910
+ .declarations()
3911
+ .contains_key(&DeclarationId::from("A::X"))
3912
+ );
3913
+ assert!(
3914
+ context
3915
+ .graph()
3916
+ .declarations()
3917
+ .contains_key(&DeclarationId::from("B::Y"))
3918
+ );
3919
+
3920
+ // SOMETHING can't be created because the circular alias can't resolve to a namespace
3921
+ assert!(
3922
+ !context
3923
+ .graph()
3924
+ .declarations()
3925
+ .contains_key(&DeclarationId::from("A::X::SOMETHING"))
3926
+ );
3927
+ }
3928
+
3929
+ #[test]
3930
+ fn resolving_constant_alias_ping_pong() {
3931
+ let mut context = GraphTest::new();
3932
+ context.index_uri("file:///foo.rb", {
3933
+ r"
3934
+ module Left
3935
+ module Deep
3936
+ VALUE = 'left'
3937
+ end
3938
+ end
3939
+
3940
+ module Right
3941
+ module Deep
3942
+ VALUE = 'right'
3943
+ end
3944
+ end
3945
+
3946
+ Left::RIGHT_REF = Right
3947
+ Right::LEFT_REF = Left
3948
+
3949
+ Left::RIGHT_REF::Deep::VALUE
3950
+ Left::RIGHT_REF::LEFT_REF::Deep::VALUE
3951
+ "
3952
+ });
3953
+ context.resolve();
3954
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3955
+
3956
+ assert_constant_alias_target_eq!(context, "Left::RIGHT_REF", "Right");
3957
+ assert_constant_alias_target_eq!(context, "Right::LEFT_REF", "Left");
3958
+
3959
+ // Left::RIGHT_REF::Deep::VALUE
3960
+ assert_constant_reference_to!(context, "Right::Deep", "file:///foo.rb:15:17-15:21");
3961
+ assert_constant_reference_to!(context, "Right::Deep::VALUE", "file:///foo.rb:15:23-15:28");
3962
+ // Left::RIGHT_REF::LEFT_REF::Deep::VALUE
3963
+ assert_constant_reference_to!(context, "Left::Deep", "file:///foo.rb:16:27-16:31");
3964
+ assert_constant_reference_to!(context, "Left::Deep::VALUE", "file:///foo.rb:16:33-16:38");
3965
+ }
3966
+
3967
+ #[test]
3968
+ fn resolving_constant_alias_self_referential() {
3969
+ let mut context = GraphTest::new();
3970
+ context.index_uri("file:///foo.rb", {
3971
+ r"
3972
+ module M
3973
+ SELF_REF = M
3974
+
3975
+ class Thing
3976
+ CONST = 1
3977
+ end
3978
+ end
3979
+
3980
+ M::SELF_REF::Thing::CONST
3981
+ M::SELF_REF::SELF_REF::Thing::CONST
3982
+ M::SELF_REF::SELF_REF::SELF_REF::Thing::CONST
3983
+ "
3984
+ });
3985
+ context.resolve();
3986
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
3987
+
3988
+ assert_constant_alias_target_eq!(context, "M::SELF_REF", "M");
3989
+
3990
+ let m_thing_const = context
3991
+ .graph()
3992
+ .declarations()
3993
+ .get(&DeclarationId::from("M::Thing::CONST"))
3994
+ .unwrap();
3995
+ let m_thing = context
3996
+ .graph()
3997
+ .declarations()
3998
+ .get(&DeclarationId::from("M::Thing"))
3999
+ .unwrap();
4000
+
4001
+ // All 3 paths resolve to M::Thing::CONST
4002
+ assert_eq!(m_thing_const.references().len(), 3);
4003
+ assert_eq!(m_thing.references().len(), 3);
4004
+
4005
+ // M::SELF_REF::Thing::CONST
4006
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:8:13-8:18");
4007
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:8:20-8:25");
4008
+ // M::SELF_REF::SELF_REF::Thing::CONST
4009
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:9:23-9:28");
4010
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:9:30-9:35");
4011
+ // M::SELF_REF::SELF_REF::SELF_REF::Thing::CONST
4012
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:10:33-10:38");
4013
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:10:40-10:45");
4014
+ }
4015
+
4016
+ #[test]
4017
+ fn resolving_class_through_constant_alias() {
4018
+ let mut context = GraphTest::new();
4019
+ context.index_uri("file:///foo.rb", {
4020
+ r"
4021
+ module Outer
4022
+ class Inner
4023
+ end
4024
+ end
4025
+
4026
+ ALIAS = Outer
4027
+ Outer::NESTED = Outer::Inner
4028
+
4029
+ class ALIAS::NESTED
4030
+ ADDED_CONST = 1
4031
+ end
4032
+
4033
+ Outer::Inner::ADDED_CONST
4034
+ "
4035
+ });
4036
+ context.resolve();
4037
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4038
+
4039
+ assert_constant_alias_target_eq!(context, "ALIAS", "Outer");
4040
+ assert_constant_alias_target_eq!(context, "Outer::NESTED", "Outer::Inner");
4041
+
4042
+ // ADDED_CONST should be in Outer::Inner (the resolved target)
4043
+ assert!(
4044
+ context
4045
+ .graph()
4046
+ .declarations()
4047
+ .contains_key(&DeclarationId::from("Outer::Inner::ADDED_CONST"))
4048
+ );
4049
+ assert!(
4050
+ context
4051
+ .graph()
4052
+ .declarations()
4053
+ .contains_key(&DeclarationId::from("Outer::Inner::ADDED_CONST"))
4054
+ );
4055
+
4056
+ let added_const = context
4057
+ .graph()
4058
+ .declarations()
4059
+ .get(&DeclarationId::from("Outer::Inner::ADDED_CONST"))
4060
+ .unwrap();
4061
+ assert_eq!(added_const.references().len(), 1);
4062
+ }
4063
+
4064
+ #[test]
4065
+ fn resolving_class_definition_through_constant_alias() {
4066
+ let mut context = GraphTest::new();
4067
+ context.index_uri("file:///foo.rb", {
4068
+ r"
4069
+ module Outer
4070
+ CONST = 1
4071
+ end
4072
+
4073
+ ALIAS = Outer
4074
+
4075
+ class ALIAS::NewClass
4076
+ CLASS_CONST = 2
4077
+ end
4078
+
4079
+ Outer::NewClass::CLASS_CONST
4080
+ ALIAS::NewClass::CLASS_CONST
4081
+ "
4082
+ });
4083
+ context.resolve();
4084
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4085
+
4086
+ assert_constant_alias_target_eq!(context, "ALIAS", "Outer");
4087
+
4088
+ // NewClass should be declared under Outer, not ALIAS
4089
+ assert!(
4090
+ context
4091
+ .graph()
4092
+ .declarations()
4093
+ .contains_key(&DeclarationId::from("Outer::NewClass"))
4094
+ );
4095
+ assert!(
4096
+ context
4097
+ .graph()
4098
+ .declarations()
4099
+ .contains_key(&DeclarationId::from("Outer::NewClass::CLASS_CONST"))
4100
+ );
4101
+
4102
+ // Outer::NewClass::CLASS_CONST
4103
+ assert_constant_reference_to!(context, "Outer::NewClass", "file:///foo.rb:10:7-10:15");
4104
+ assert_constant_reference_to!(context, "Outer::NewClass::CLASS_CONST", "file:///foo.rb:10:17-10:28");
4105
+ // ALIAS::NewClass::CLASS_CONST
4106
+ assert_constant_reference_to!(context, "Outer::NewClass", "file:///foo.rb:11:7-11:15");
4107
+ assert_constant_reference_to!(context, "Outer::NewClass::CLASS_CONST", "file:///foo.rb:11:17-11:28");
4108
+ }
4109
+
4110
+ #[test]
4111
+ fn resolving_constant_alias_with_multiple_definitions() {
4112
+ let mut context = GraphTest::new();
4113
+ context.index_uri("file:///a.rb", {
4114
+ r"
4115
+ module A; end
4116
+ FOO = A
4117
+ "
4118
+ });
4119
+ context.index_uri("file:///b.rb", {
4120
+ r"
4121
+ module B; end
4122
+ FOO = B
4123
+ "
4124
+ });
4125
+ context.resolve();
4126
+ assert_no_diagnostics!(&context);
4127
+
4128
+ // FOO should have 2 definitions pointing to different targets
4129
+ assert_eq!(
4130
+ context
4131
+ .graph()
4132
+ .declarations()
4133
+ .get(&DeclarationId::from("FOO"))
4134
+ .unwrap()
4135
+ .definitions()
4136
+ .len(),
4137
+ 2
4138
+ );
4139
+
4140
+ assert_alias_targets_contain!(context, "FOO", "A", "B");
4141
+ }
4142
+
4143
+ #[test]
4144
+ fn resolving_constant_alias_with_multiple_targets() {
4145
+ let mut context = GraphTest::new();
4146
+ context.index_uri("file:///a.rb", {
4147
+ r"
4148
+ module A
4149
+ CONST_A = 1
4150
+ end
4151
+ FOO = A
4152
+ "
4153
+ });
4154
+ context.index_uri("file:///b.rb", {
4155
+ r"
4156
+ module B
4157
+ CONST_B = 2
4158
+ end
4159
+ FOO = B
4160
+ "
4161
+ });
4162
+ context.index_uri("file:///usage.rb", {
4163
+ r"
4164
+ FOO::CONST_A
4165
+ FOO::CONST_B
4166
+ "
4167
+ });
4168
+ context.resolve();
4169
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4170
+
4171
+ // FOO::CONST_A should resolve to A::CONST_A
4172
+ assert_constant_reference_to!(context, "A::CONST_A", "file:///usage.rb:0:5-0:12");
4173
+ // FOO::CONST_B should resolve to B::CONST_B
4174
+ assert_constant_reference_to!(context, "B::CONST_B", "file:///usage.rb:1:5-1:12");
4175
+ }
4176
+
4177
+ #[test]
4178
+ fn resolving_constant_alias_multi_target_with_circular() {
4179
+ let mut context = GraphTest::new();
4180
+ context.index_uri("file:///a.rb", {
4181
+ r"
4182
+ module A
4183
+ CONST = 1
4184
+ end
4185
+ ALIAS = A
4186
+ "
4187
+ });
4188
+ context.index_uri("file:///b.rb", "ALIAS = ALIAS");
4189
+ context.index_uri("file:///usage.rb", "ALIAS::CONST");
4190
+ context.resolve();
4191
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4192
+
4193
+ // ALIAS should have two targets: A and ALIAS (self-reference)
4194
+ assert_alias_targets_contain!(context, "ALIAS", "A", "ALIAS");
4195
+
4196
+ // ALIAS::CONST should still resolve to A::CONST through the valid path
4197
+ assert_constant_reference_to!(context, "A::CONST", "file:///usage.rb:0:7-0:12");
4198
+ }
4199
+
4200
+ #[test]
4201
+ fn resolving_constant_reference_through_chained_aliases() {
4202
+ let mut context = GraphTest::new();
4203
+ context.index_uri("file:///defs.rb", {
4204
+ r"
4205
+ module Foo
4206
+ CONST = 1
4207
+ end
4208
+ ALIAS1 = Foo
4209
+ ALIAS2 = ALIAS1
4210
+ "
4211
+ });
4212
+ context.index_uri("file:///usage.rb", "ALIAS2::CONST");
4213
+ context.resolve();
4214
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4215
+
4216
+ assert_constant_alias_target_eq!(context, "ALIAS1", "Foo");
4217
+ assert_constant_alias_target_eq!(context, "ALIAS2", "ALIAS1");
4218
+
4219
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///usage.rb:0:8-0:13");
4220
+ }
4221
+
4222
+ #[test]
4223
+ fn resolving_constant_reference_through_top_level_alias_target() {
4224
+ let mut context = GraphTest::new();
4225
+ context.index_uri("file:///defs.rb", {
4226
+ r"
4227
+ module Foo
4228
+ CONST = 1
4229
+ end
4230
+ ALIAS = ::Foo
4231
+ "
4232
+ });
4233
+ context.index_uri("file:///usage.rb", "ALIAS::CONST");
4234
+ context.resolve();
4235
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4236
+
4237
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///usage.rb:0:7-0:12");
4238
+ }
4239
+
4240
+ // Regression test: defining singleton method on alias triggers get_or_create_singleton_class
4241
+ #[test]
4242
+ fn resolving_singleton_method_on_alias_does_not_panic() {
4243
+ let mut context = GraphTest::new();
4244
+ context.index_uri("file:///foo.rb", {
4245
+ r"
4246
+ class Foo; end
4247
+ ALIAS = Foo
4248
+ def ALIAS.singleton_method; end
4249
+ "
4250
+ });
4251
+ context.resolve();
4252
+ assert_no_diagnostics!(&context);
4253
+ }
4254
+
4255
+ #[test]
4256
+ fn multi_target_alias_constant_added_to_primary_owner() {
4257
+ let mut context = GraphTest::new();
4258
+ context.index_uri("file:///modules.rb", {
4259
+ r"
4260
+ module Foo; end
4261
+ module Bar; end
4262
+ "
4263
+ });
4264
+ context.index_uri("file:///alias1.rb", {
4265
+ r"
4266
+ ALIAS ||= Foo
4267
+ "
4268
+ });
4269
+ context.index_uri("file:///alias2.rb", {
4270
+ r"
4271
+ ALIAS ||= Bar
4272
+ "
4273
+ });
4274
+ context.index_uri("file:///const.rb", {
4275
+ r"
4276
+ ALIAS::CONST = 123
4277
+ "
4278
+ });
4279
+ context.resolve();
4280
+
4281
+ assert_no_diagnostics!(&context);
4282
+
4283
+ assert_members_eq!(context, "Foo", vec!["CONST"]);
4284
+ assert_no_members!(context, "Bar");
4285
+ }
4286
+
4287
+ #[test]
4288
+ fn distinct_declarations_with_conflicting_string_ids() {
4289
+ let mut context = GraphTest::new();
4290
+ context.index_uri("file:///foo.rb", {
4291
+ r"
4292
+ class Foo
4293
+ def Array(); end
4294
+ class Array; end
4295
+ end
4296
+ "
4297
+ });
4298
+ context.resolve();
4299
+
4300
+ assert_no_diagnostics!(&context);
4301
+
4302
+ // Both entries exist as unique members
4303
+ assert_members_eq!(context, "Foo", vec!["Array", "Array()"]);
4304
+
4305
+ // Both declarations exist with unique IDs
4306
+ assert!(
4307
+ context
4308
+ .graph()
4309
+ .declarations()
4310
+ .get(&DeclarationId::from("Foo::Array"))
4311
+ .is_some()
4312
+ );
4313
+ assert!(
4314
+ context
4315
+ .graph()
4316
+ .declarations()
4317
+ .get(&DeclarationId::from("Foo#Array()"))
4318
+ .is_some()
4319
+ );
4320
+ }
4321
+
4322
+ #[test]
4323
+ fn fully_qualified_names_are_unique() {
4324
+ let mut context = GraphTest::new();
4325
+ context.index_uri("file:///foo.rb", {
4326
+ r"
4327
+ module Foo
4328
+ class Bar
4329
+ CONST = 1
4330
+ @class_ivar = 2
4331
+
4332
+ attr_reader :baz
4333
+ attr_writer :qux
4334
+ attr_accessor :zip
4335
+
4336
+ def instance_m
4337
+ @@class_var = 3
4338
+ end
4339
+
4340
+ def self.singleton_m
4341
+ $global_var = 4
4342
+ end
4343
+
4344
+ def Foo.another_singleton_m; end
4345
+
4346
+ class << self
4347
+ OTHER_CONST = 5
4348
+ @other_class_ivar = 6
4349
+ @@other_class_var = 7
4350
+
4351
+ def other_instance_m
4352
+ @my_class_var = 8
4353
+ end
4354
+
4355
+ def self.other_singleton_m
4356
+ $other_global_var = 9
4357
+ end
4358
+ end
4359
+ end
4360
+ end
4361
+ "
4362
+ });
4363
+ context.resolve();
4364
+
4365
+ let declarations = context.graph().declarations();
4366
+
4367
+ // In the same order of appearence
4368
+ assert!(declarations.contains_key(&DeclarationId::from("Foo")));
4369
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar")));
4370
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::CONST")));
4371
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>#@class_ivar")));
4372
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#baz()")));
4373
+ // TODO: needs the fix for attributes
4374
+ // assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#qux=()")));
4375
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#zip()")));
4376
+ // TODO: needs the fix for attributes
4377
+ // assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#zip=()")));
4378
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#instance_m()")));
4379
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#@@class_var")));
4380
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>#singleton_m()")));
4381
+ assert!(declarations.contains_key(&DeclarationId::from("$global_var")));
4382
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::<Foo>#another_singleton_m()")));
4383
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>::OTHER_CONST")));
4384
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>::<<Bar>>#@other_class_ivar")));
4385
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar#@@other_class_var")));
4386
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>#other_instance_m()")));
4387
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>#@my_class_var")));
4388
+ assert!(declarations.contains_key(&DeclarationId::from("Foo::Bar::<Bar>::<<Bar>>#other_singleton_m()")));
4389
+ assert!(declarations.contains_key(&DeclarationId::from("$other_global_var")));
4390
+ }
4391
+
4392
+ #[test]
4393
+ fn test_nested_same_names() {
4394
+ let mut context = GraphTest::new();
4395
+ context.index_uri("file:///foo.rb", {
4396
+ r"
4397
+ module Foo; end
4398
+
4399
+ module Bar
4400
+ Foo
4401
+
4402
+ module Foo
4403
+ FOO = 42
4404
+ end
4405
+ end
4406
+ "
4407
+ });
4408
+ context.resolve();
4409
+
4410
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4411
+
4412
+ // FIXME: this is wrong, the reference is not to `Bar::Foo`, but to `Foo`
4413
+ assert_constant_reference_to!(context, "Bar::Foo", "file:///foo.rb:3:2-3:5");
4414
+
4415
+ assert_ancestors_eq!(context, "Foo", &["Foo"]);
4416
+ assert_ancestors_eq!(context, "Bar::Foo", &["Bar::Foo"]);
4417
+
4418
+ assert_no_members!(context, "Foo");
4419
+ assert_members_eq!(context, "Bar::Foo", vec!["FOO"]);
4420
+ }
4421
+ }