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.
- checksums.yaml +4 -4
- data/ext/rubydex/declaration.c +146 -0
- data/ext/rubydex/declaration.h +10 -0
- data/ext/rubydex/definition.c +234 -0
- data/ext/rubydex/definition.h +28 -0
- data/ext/rubydex/diagnostic.c +6 -0
- data/ext/rubydex/diagnostic.h +11 -0
- data/ext/rubydex/document.c +98 -0
- data/ext/rubydex/document.h +10 -0
- data/ext/rubydex/extconf.rb +36 -15
- data/ext/rubydex/graph.c +405 -0
- data/ext/rubydex/graph.h +10 -0
- data/ext/rubydex/handle.h +44 -0
- data/ext/rubydex/location.c +22 -0
- data/ext/rubydex/location.h +15 -0
- data/ext/rubydex/reference.c +104 -0
- data/ext/rubydex/reference.h +16 -0
- data/ext/rubydex/rubydex.c +22 -0
- data/ext/rubydex/utils.c +27 -0
- data/ext/rubydex/utils.h +13 -0
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rust/Cargo.lock +1275 -0
- data/rust/Cargo.toml +23 -0
- data/rust/about.hbs +78 -0
- data/rust/about.toml +9 -0
- data/rust/rubydex/Cargo.toml +41 -0
- data/rust/rubydex/src/diagnostic.rs +108 -0
- data/rust/rubydex/src/errors.rs +28 -0
- data/rust/rubydex/src/indexing/local_graph.rs +172 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +5397 -0
- data/rust/rubydex/src/indexing.rs +128 -0
- data/rust/rubydex/src/job_queue.rs +186 -0
- data/rust/rubydex/src/lib.rs +15 -0
- data/rust/rubydex/src/listing.rs +249 -0
- data/rust/rubydex/src/main.rs +116 -0
- data/rust/rubydex/src/model/comment.rs +24 -0
- data/rust/rubydex/src/model/declaration.rs +541 -0
- data/rust/rubydex/src/model/definitions.rs +1475 -0
- data/rust/rubydex/src/model/document.rs +111 -0
- data/rust/rubydex/src/model/encoding.rs +22 -0
- data/rust/rubydex/src/model/graph.rs +1387 -0
- data/rust/rubydex/src/model/id.rs +90 -0
- data/rust/rubydex/src/model/identity_maps.rs +54 -0
- data/rust/rubydex/src/model/ids.rs +32 -0
- data/rust/rubydex/src/model/name.rs +188 -0
- data/rust/rubydex/src/model/references.rs +129 -0
- data/rust/rubydex/src/model/string_ref.rs +44 -0
- data/rust/rubydex/src/model/visibility.rs +41 -0
- data/rust/rubydex/src/model.rs +13 -0
- data/rust/rubydex/src/offset.rs +70 -0
- data/rust/rubydex/src/position.rs +6 -0
- data/rust/rubydex/src/query.rs +103 -0
- data/rust/rubydex/src/resolution.rs +4421 -0
- data/rust/rubydex/src/stats/memory.rs +71 -0
- data/rust/rubydex/src/stats/timer.rs +126 -0
- data/rust/rubydex/src/stats.rs +9 -0
- data/rust/rubydex/src/test_utils/context.rs +226 -0
- data/rust/rubydex/src/test_utils/graph_test.rs +229 -0
- data/rust/rubydex/src/test_utils/local_graph_test.rs +166 -0
- data/rust/rubydex/src/test_utils.rs +52 -0
- data/rust/rubydex/src/visualization/dot.rs +176 -0
- data/rust/rubydex/src/visualization.rs +6 -0
- data/rust/rubydex/tests/cli.rs +167 -0
- data/rust/rubydex-sys/Cargo.toml +20 -0
- data/rust/rubydex-sys/build.rs +14 -0
- data/rust/rubydex-sys/cbindgen.toml +12 -0
- data/rust/rubydex-sys/src/declaration_api.rs +114 -0
- data/rust/rubydex-sys/src/definition_api.rs +350 -0
- data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
- data/rust/rubydex-sys/src/document_api.rs +54 -0
- data/rust/rubydex-sys/src/graph_api.rs +493 -0
- data/rust/rubydex-sys/src/lib.rs +9 -0
- data/rust/rubydex-sys/src/location_api.rs +79 -0
- data/rust/rubydex-sys/src/name_api.rs +81 -0
- data/rust/rubydex-sys/src/reference_api.rs +191 -0
- data/rust/rubydex-sys/src/utils.rs +50 -0
- data/rust/rustfmt.toml +2 -0
- 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(¤t) {
|
|
1119
|
+
Some(Declaration::ConstantAlias(_)) => {
|
|
1120
|
+
let targets = self.graph.alias_targets(¤t).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
|
+
}
|