rubydex 0.2.6 → 0.2.8
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/THIRD_PARTY_LICENSES.html +39 -6
- data/exe/rdx +2 -0
- data/ext/rubydex/declaration.c +115 -106
- data/ext/rubydex/definition.c +100 -80
- data/ext/rubydex/diagnostic.c +5 -0
- data/ext/rubydex/document.c +23 -31
- data/ext/rubydex/extconf.rb +1 -1
- data/ext/rubydex/graph.c +257 -70
- data/ext/rubydex/handle.h +13 -0
- data/ext/rubydex/location.c +5 -0
- data/ext/rubydex/reference.c +51 -46
- data/ext/rubydex/rubydex.c +5 -0
- data/ext/rubydex/signature.c +5 -0
- data/ext/rubydex/utils.c +13 -0
- data/ext/rubydex/utils.h +4 -0
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/errors.rb +3 -0
- data/lib/rubydex/graph.rb +9 -28
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +15 -7
- data/rust/Cargo.lock +119 -14
- data/rust/rubydex/Cargo.toml +19 -1
- data/rust/rubydex/benches/graph_memory.rs +46 -0
- data/rust/rubydex/src/config.rs +275 -0
- data/rust/rubydex/src/errors.rs +2 -0
- data/rust/rubydex/src/lib.rs +7 -0
- data/rust/rubydex/src/main.rs +148 -5
- data/rust/rubydex/src/model/declaration.rs +16 -55
- data/rust/rubydex/src/model/graph.rs +123 -17
- data/rust/rubydex/src/model/ids.rs +30 -0
- data/rust/rubydex/src/model/string_ref.rs +12 -4
- data/rust/rubydex/src/query.rs +83 -15
- data/rust/rubydex/src/resolution.rs +198 -151
- data/rust/rubydex/tests/cli.rs +66 -0
- data/rust/rubydex-mcp/src/server.rs +1 -1
- data/rust/rubydex-sys/src/declaration_api.rs +2 -2
- data/rust/rubydex-sys/src/graph_api.rs +127 -46
- metadata +4 -2
|
@@ -19,26 +19,37 @@ use crate::model::{
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
enum Outcome {
|
|
22
|
-
/// The constant was successfully resolved to the given declaration ID.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
///
|
|
26
|
-
|
|
27
|
-
///
|
|
28
|
-
|
|
29
|
-
///
|
|
30
|
-
///
|
|
31
|
-
|
|
32
|
-
/// needs to be linearized before we can retry.
|
|
33
|
-
Retry(Option<DeclarationId>),
|
|
22
|
+
/// The constant was successfully resolved to the given declaration ID.
|
|
23
|
+
Resolved(DeclarationId),
|
|
24
|
+
/// We had everything we needed to resolve this constant (all relevant ancestor chains are complete), but the member
|
|
25
|
+
/// isn't there. This is definitive: the unit can be dropped and a diagnostic emitted.
|
|
26
|
+
Unresolved,
|
|
27
|
+
/// We couldn't resolve this constant right now because a dependency was missing, so it may succeed later. The unit
|
|
28
|
+
/// must be placed back in the queue and retried once we have progressed further. `partial` is `true` when the
|
|
29
|
+
/// blocker was a still-partial ancestor chain (the member may appear once the chain completes); `false` when it was
|
|
30
|
+
/// a plain missing dependency (an unresolved parent scope, alias, or lexical nesting).
|
|
31
|
+
Retry { partial_ancestors: bool },
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
impl Outcome {
|
|
37
35
|
fn is_resolved_or_retry(&self) -> bool {
|
|
38
|
-
matches!(self, Outcome::Resolved(_
|
|
36
|
+
matches!(self, Outcome::Resolved(_) | Outcome::Retry { .. })
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
|
|
40
|
+
/// Controls how `get_or_create_singleton_class` schedules linearization of the singleton it touches.
|
|
41
|
+
#[derive(Clone, Copy)]
|
|
42
|
+
enum SingletonAncestors {
|
|
43
|
+
/// Linearize the ancestors inline, immediately. Should only be used after the convergence loop, when every
|
|
44
|
+
/// namespace is already resolved.
|
|
45
|
+
Eager,
|
|
46
|
+
/// Enqueue a `Unit::Ancestors` so the convergence loop linearizes it on a later pass.
|
|
47
|
+
Enqueue,
|
|
48
|
+
/// Do nothing: the caller is already mid-linearization and will linearize this singleton inline
|
|
49
|
+
/// within the current `LinearizationContext`, so a separate unit would be pure duplicate work.
|
|
50
|
+
Deferred,
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
struct LinearizationContext {
|
|
43
54
|
descendants: IdentityHashSet<DeclarationId>,
|
|
44
55
|
seen_ids: IdentityHashSet<DeclarationId>,
|
|
@@ -140,8 +151,8 @@ impl<'a> Resolver<'a> {
|
|
|
140
151
|
/// the Ruby API
|
|
141
152
|
pub fn resolve_constant(&mut self, name_id: NameId) -> Option<DeclarationId> {
|
|
142
153
|
match self.resolve_constant_internal(name_id) {
|
|
143
|
-
Outcome::Resolved(id
|
|
144
|
-
Outcome::Unresolved
|
|
154
|
+
Outcome::Resolved(id) => Some(id),
|
|
155
|
+
Outcome::Unresolved | Outcome::Retry { .. } => None,
|
|
145
156
|
}
|
|
146
157
|
}
|
|
147
158
|
|
|
@@ -186,45 +197,40 @@ impl<'a> Resolver<'a> {
|
|
|
186
197
|
};
|
|
187
198
|
let str_id = *method.str_id();
|
|
188
199
|
match self.graph.definition_id_to_declaration_id(*def_id) {
|
|
189
|
-
Some(&owner_decl_id) =>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
Some(&owner_decl_id) => {
|
|
201
|
+
match self.get_or_create_singleton_class(owner_decl_id, SingletonAncestors::Enqueue) {
|
|
202
|
+
Some(singleton_id) => {
|
|
203
|
+
self.create_declaration(str_id, id, singleton_id, |name| {
|
|
204
|
+
Declaration::Method(Box::new(MethodDeclaration::new(name, singleton_id)))
|
|
205
|
+
});
|
|
206
|
+
Outcome::Resolved(singleton_id)
|
|
207
|
+
}
|
|
208
|
+
// Owner is a non-promotable constant — method is orphaned
|
|
209
|
+
None => Outcome::Unresolved,
|
|
195
210
|
}
|
|
196
|
-
|
|
197
|
-
None => Outcome::Unresolved(None),
|
|
198
|
-
},
|
|
211
|
+
}
|
|
199
212
|
// Owning class not resolved yet — retry next pass
|
|
200
|
-
None => Outcome::Retry
|
|
213
|
+
None => Outcome::Retry {
|
|
214
|
+
partial_ancestors: false,
|
|
215
|
+
},
|
|
201
216
|
}
|
|
202
217
|
}
|
|
203
218
|
_ => panic!("Expected constant or singleton method definitions"),
|
|
204
219
|
};
|
|
205
220
|
|
|
206
221
|
match outcome {
|
|
207
|
-
Outcome::Retry
|
|
208
|
-
// There might be dependencies we haven't figured out yet, so we need to retry
|
|
222
|
+
Outcome::Retry { .. } => {
|
|
209
223
|
self.unit_queue.push_back(unit_id);
|
|
210
224
|
}
|
|
211
|
-
Outcome::Unresolved
|
|
225
|
+
Outcome::Unresolved => {
|
|
212
226
|
// We couldn't resolve this name. Emit a diagnostic
|
|
213
227
|
}
|
|
214
|
-
Outcome::
|
|
215
|
-
self.unit_queue.push_back(unit_id);
|
|
216
|
-
self.unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
|
|
217
|
-
}
|
|
218
|
-
Outcome::Resolved(id, None) => {
|
|
228
|
+
Outcome::Resolved(id) => {
|
|
219
229
|
if needs_linearization {
|
|
220
230
|
self.unit_queue.push_back(Unit::Ancestors(id));
|
|
221
231
|
}
|
|
222
232
|
self.made_progress = true;
|
|
223
233
|
}
|
|
224
|
-
Outcome::Resolved(_, Some(id_needing_linearization)) => {
|
|
225
|
-
self.unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
|
|
226
|
-
self.made_progress = true;
|
|
227
|
-
}
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
236
|
|
|
@@ -233,24 +239,17 @@ impl<'a> Resolver<'a> {
|
|
|
233
239
|
let constant_ref = self.graph.constant_references().get(&id).unwrap();
|
|
234
240
|
|
|
235
241
|
match self.resolve_constant_internal(*constant_ref.name_id()) {
|
|
236
|
-
Outcome::
|
|
237
|
-
// Retry: dependencies not resolved yet, or name genuinely unknown
|
|
238
|
-
// (which can be temporary during incremental invalidation when the
|
|
239
|
-
// parent namespace was deleted but will be re-added).
|
|
240
|
-
self.unit_queue.push_back(unit_id);
|
|
241
|
-
}
|
|
242
|
-
Outcome::Retry(Some(id_needing_linearization)) | Outcome::Unresolved(Some(id_needing_linearization)) => {
|
|
243
|
-
self.unit_queue.push_back(unit_id);
|
|
244
|
-
self.unit_queue.push_back(Unit::Ancestors(id_needing_linearization));
|
|
245
|
-
}
|
|
246
|
-
Outcome::Resolved(declaration_id, None) => {
|
|
242
|
+
Outcome::Resolved(declaration_id) => {
|
|
247
243
|
self.graph.record_resolved_reference(id, declaration_id);
|
|
248
244
|
self.made_progress = true;
|
|
249
245
|
}
|
|
250
|
-
Outcome::
|
|
251
|
-
self.
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
Outcome::Retry { .. } => {
|
|
247
|
+
self.unit_queue.push_back(unit_id);
|
|
248
|
+
}
|
|
249
|
+
Outcome::Unresolved => {
|
|
250
|
+
// If we had everything we needed to resolve this constant and still failed, then remember it as work
|
|
251
|
+
// for the next incremental resolution pass, but avoid trying again in this cycle
|
|
252
|
+
self.graph.push_work(unit_id);
|
|
254
253
|
}
|
|
255
254
|
}
|
|
256
255
|
}
|
|
@@ -291,7 +290,9 @@ impl<'a> Resolver<'a> {
|
|
|
291
290
|
continue;
|
|
292
291
|
};
|
|
293
292
|
|
|
294
|
-
let Some(singleton_id) =
|
|
293
|
+
let Some(singleton_id) =
|
|
294
|
+
self.get_or_create_singleton_class(receiver_decl_id, SingletonAncestors::Eager)
|
|
295
|
+
else {
|
|
295
296
|
continue;
|
|
296
297
|
};
|
|
297
298
|
|
|
@@ -395,7 +396,9 @@ impl<'a> Resolver<'a> {
|
|
|
395
396
|
};
|
|
396
397
|
|
|
397
398
|
// Instance variable in singleton method - owned by the receiver's singleton class
|
|
398
|
-
let Some(owner_id) =
|
|
399
|
+
let Some(owner_id) =
|
|
400
|
+
self.get_or_create_singleton_class(receiver_decl_id, SingletonAncestors::Eager)
|
|
401
|
+
else {
|
|
399
402
|
continue;
|
|
400
403
|
};
|
|
401
404
|
{
|
|
@@ -453,7 +456,9 @@ impl<'a> Resolver<'a> {
|
|
|
453
456
|
.copied()
|
|
454
457
|
.unwrap_or(*OBJECT_ID);
|
|
455
458
|
|
|
456
|
-
let Some(owner_id) =
|
|
459
|
+
let Some(owner_id) =
|
|
460
|
+
self.get_or_create_singleton_class(nesting_decl_id, SingletonAncestors::Eager)
|
|
461
|
+
else {
|
|
457
462
|
continue;
|
|
458
463
|
};
|
|
459
464
|
{
|
|
@@ -483,7 +488,7 @@ impl<'a> Resolver<'a> {
|
|
|
483
488
|
continue;
|
|
484
489
|
};
|
|
485
490
|
let owner_id = self
|
|
486
|
-
.get_or_create_singleton_class(singleton_class_decl_id,
|
|
491
|
+
.get_or_create_singleton_class(singleton_class_decl_id, SingletonAncestors::Eager)
|
|
487
492
|
.expect("singleton class nesting should always be a namespace");
|
|
488
493
|
{
|
|
489
494
|
debug_assert!(
|
|
@@ -524,7 +529,8 @@ impl<'a> Resolver<'a> {
|
|
|
524
529
|
continue;
|
|
525
530
|
};
|
|
526
531
|
|
|
527
|
-
let Some(owner_id) = self.get_or_create_singleton_class(decl_id,
|
|
532
|
+
let Some(owner_id) = self.get_or_create_singleton_class(decl_id, SingletonAncestors::Eager)
|
|
533
|
+
else {
|
|
528
534
|
continue;
|
|
529
535
|
};
|
|
530
536
|
|
|
@@ -652,7 +658,9 @@ impl<'a> Resolver<'a> {
|
|
|
652
658
|
};
|
|
653
659
|
|
|
654
660
|
let owner_id = if is_singleton {
|
|
655
|
-
let Some(singleton_id) =
|
|
661
|
+
let Some(singleton_id) =
|
|
662
|
+
self.get_or_create_singleton_class(lexical_owner_id, SingletonAncestors::Eager)
|
|
663
|
+
else {
|
|
656
664
|
continue;
|
|
657
665
|
};
|
|
658
666
|
singleton_id
|
|
@@ -710,18 +718,8 @@ impl<'a> Resolver<'a> {
|
|
|
710
718
|
offset,
|
|
711
719
|
format!("undefined method `{owner_name}#{method_name}` for visibility change"),
|
|
712
720
|
);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
// visibility resolution) and won't be cleaned up on file delete, so attaching
|
|
716
|
-
// the diagnostic to the declaration would leave it orphaned.
|
|
717
|
-
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
718
|
-
} else {
|
|
719
|
-
self.graph
|
|
720
|
-
.declarations_mut()
|
|
721
|
-
.get_mut(&owner_id)
|
|
722
|
-
.unwrap()
|
|
723
|
-
.add_diagnostic(diagnostic);
|
|
724
|
-
}
|
|
721
|
+
|
|
722
|
+
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
725
723
|
}
|
|
726
724
|
}
|
|
727
725
|
|
|
@@ -855,19 +853,20 @@ impl<'a> Resolver<'a> {
|
|
|
855
853
|
/// If the declaration is a `Constant` with all-promotable definitions, it is automatically promoted to a `Class`
|
|
856
854
|
/// namespace before creating the singleton. Returns `None` if the declaration is not a namespace and cannot be
|
|
857
855
|
/// promoted (e.g., `FOO = 42`).
|
|
858
|
-
///
|
|
859
|
-
///
|
|
856
|
+
/// `ancestors` controls how the singleton's ancestor chain is scheduled for linearization —
|
|
857
|
+
/// inline now (`Eager`), enqueued for the convergence loop (`Enqueue`), or left to the caller
|
|
858
|
+
/// that is already linearizing it inline (`Deferred`). See [`SingletonAncestors`].
|
|
860
859
|
fn get_or_create_singleton_class(
|
|
861
860
|
&mut self,
|
|
862
861
|
attached_id: DeclarationId,
|
|
863
|
-
|
|
862
|
+
mode: SingletonAncestors,
|
|
864
863
|
) -> Option<DeclarationId> {
|
|
865
864
|
let attached_decl = self.graph.declarations().get(&attached_id).unwrap();
|
|
866
865
|
|
|
867
866
|
// If the attached object is a constant alias, follow the alias chain to find the actual namespace
|
|
868
867
|
if matches!(attached_decl, Declaration::ConstantAlias(_)) {
|
|
869
868
|
return match self.resolve_to_namespace(attached_id) {
|
|
870
|
-
Some(id) => self.get_or_create_singleton_class(id,
|
|
869
|
+
Some(id) => self.get_or_create_singleton_class(id, mode),
|
|
871
870
|
None => None,
|
|
872
871
|
};
|
|
873
872
|
}
|
|
@@ -878,11 +877,7 @@ impl<'a> Resolver<'a> {
|
|
|
878
877
|
Declaration::Namespace(Namespace::Module(Box::new(ModuleDeclaration::new(name, owner_id))))
|
|
879
878
|
});
|
|
880
879
|
|
|
881
|
-
|
|
882
|
-
let _ = self.ancestors_of(attached_id);
|
|
883
|
-
} else {
|
|
884
|
-
self.unit_queue.push_back(Unit::Ancestors(attached_id));
|
|
885
|
-
}
|
|
880
|
+
self.schedule_singleton_ancestors(attached_id, mode);
|
|
886
881
|
} else {
|
|
887
882
|
return None;
|
|
888
883
|
}
|
|
@@ -910,15 +905,26 @@ impl<'a> Resolver<'a> {
|
|
|
910
905
|
)))),
|
|
911
906
|
);
|
|
912
907
|
|
|
913
|
-
|
|
914
|
-
let _ = self.ancestors_of(decl_id);
|
|
915
|
-
} else {
|
|
916
|
-
self.unit_queue.push_back(Unit::Ancestors(decl_id));
|
|
917
|
-
}
|
|
908
|
+
self.schedule_singleton_ancestors(decl_id, mode);
|
|
918
909
|
|
|
919
910
|
Some(decl_id)
|
|
920
911
|
}
|
|
921
912
|
|
|
913
|
+
/// Schedules linearization of a singleton's ancestor chain according to the requested policy.
|
|
914
|
+
fn schedule_singleton_ancestors(&mut self, id: DeclarationId, mode: SingletonAncestors) {
|
|
915
|
+
match mode {
|
|
916
|
+
SingletonAncestors::Eager => {
|
|
917
|
+
let _ = self.ancestors_of(id);
|
|
918
|
+
}
|
|
919
|
+
SingletonAncestors::Enqueue => {
|
|
920
|
+
self.unit_queue.push_back(Unit::Ancestors(id));
|
|
921
|
+
}
|
|
922
|
+
// The caller is already linearizing this singleton inline within the current
|
|
923
|
+
// `LinearizationContext`, so a separate unit would be redundant work.
|
|
924
|
+
SingletonAncestors::Deferred => {}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
922
928
|
/// Linearizes the ancestors of a declaration, returning the list of ancestor declaration IDs
|
|
923
929
|
///
|
|
924
930
|
/// # Panics
|
|
@@ -1021,7 +1027,7 @@ impl<'a> Resolver<'a> {
|
|
|
1021
1027
|
|
|
1022
1028
|
// Ensure that we create the singleton and enqueue it for linearization if we see an extend
|
|
1023
1029
|
if has_extends && !is_singleton_class {
|
|
1024
|
-
self.get_or_create_singleton_class(declaration_id,
|
|
1030
|
+
self.get_or_create_singleton_class(declaration_id, SingletonAncestors::Enqueue);
|
|
1025
1031
|
}
|
|
1026
1032
|
|
|
1027
1033
|
let (linearized_prepends, linearized_includes) =
|
|
@@ -1261,15 +1267,17 @@ impl<'a> Resolver<'a> {
|
|
|
1261
1267
|
// `set_singleton_class_id`, not `add_member`, so a TODO receiver would never gain a
|
|
1262
1268
|
// member. Emit Retry so the unit is preserved for a later resolve where the receiver
|
|
1263
1269
|
// may exist.
|
|
1264
|
-
Outcome::Unresolved
|
|
1265
|
-
|
|
1270
|
+
Outcome::Unresolved if singleton => Outcome::Retry {
|
|
1271
|
+
partial_ancestors: false,
|
|
1272
|
+
},
|
|
1273
|
+
Outcome::Unresolved => Outcome::Resolved(self.create_todo_for_parent(name_id)),
|
|
1266
1274
|
other => other,
|
|
1267
1275
|
};
|
|
1268
1276
|
|
|
1269
1277
|
// The name of the declaration is determined by the name of its owner, which may or may not require resolution
|
|
1270
1278
|
// depending on whether the name has a parent scope
|
|
1271
1279
|
match outcome {
|
|
1272
|
-
Outcome::Resolved(owner_id
|
|
1280
|
+
Outcome::Resolved(owner_id) => {
|
|
1273
1281
|
let mut fully_qualified_name = self.graph.strings().get(&str_id).unwrap().to_string();
|
|
1274
1282
|
|
|
1275
1283
|
// If the owner is a promotable constant and something is being defined inside it, promote it to a
|
|
@@ -1294,7 +1302,7 @@ impl<'a> Resolver<'a> {
|
|
|
1294
1302
|
// Foo = 1
|
|
1295
1303
|
// class << Foo; end
|
|
1296
1304
|
if singleton && !owner_is_namespace {
|
|
1297
|
-
return Outcome::Unresolved
|
|
1305
|
+
return Outcome::Unresolved;
|
|
1298
1306
|
}
|
|
1299
1307
|
|
|
1300
1308
|
// We don't prefix declarations with `Object::`
|
|
@@ -1324,7 +1332,7 @@ impl<'a> Resolver<'a> {
|
|
|
1324
1332
|
}
|
|
1325
1333
|
|
|
1326
1334
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1327
|
-
Outcome::Resolved(declaration_id
|
|
1335
|
+
Outcome::Resolved(declaration_id)
|
|
1328
1336
|
}
|
|
1329
1337
|
other => other,
|
|
1330
1338
|
}
|
|
@@ -1344,12 +1352,14 @@ impl<'a> Resolver<'a> {
|
|
|
1344
1352
|
// If we have `A::B`, the owner of `B` is whatever `A` resolves to.
|
|
1345
1353
|
// If `A` is an alias, resolve through to get the actual namespace.
|
|
1346
1354
|
match self.resolve_constant_internal(parent_scope) {
|
|
1347
|
-
Outcome::Resolved(id
|
|
1355
|
+
Outcome::Resolved(id) => self.resolve_to_primary_namespace(id),
|
|
1348
1356
|
// The parent scope is genuinely unknown — not a circular alias or pending
|
|
1349
1357
|
// linearization, but a name that doesn't exist anywhere in the graph.
|
|
1350
|
-
Outcome::Unresolved
|
|
1351
|
-
Outcome::Retry
|
|
1352
|
-
|
|
1358
|
+
Outcome::Unresolved => Outcome::Unresolved,
|
|
1359
|
+
Outcome::Retry {
|
|
1360
|
+
partial_ancestors: false,
|
|
1361
|
+
} if !preserve_retry => Outcome::Unresolved,
|
|
1362
|
+
retry @ Outcome::Retry { .. } => retry,
|
|
1353
1363
|
}
|
|
1354
1364
|
} else if let Some(nesting_id) = name_ref.nesting()
|
|
1355
1365
|
&& !name_ref.parent_scope().is_top_level()
|
|
@@ -1360,16 +1370,18 @@ impl<'a> Resolver<'a> {
|
|
|
1360
1370
|
// end
|
|
1361
1371
|
// If `ALIAS` points to `Outer`, `CONST` should be owned by `Outer::Target`, not `ALIAS::Target`.
|
|
1362
1372
|
match self.graph.names().get(nesting_id).unwrap() {
|
|
1363
|
-
NameRef::Resolved(resolved) => self.resolve_to_primary_namespace(*resolved.declaration_id()
|
|
1373
|
+
NameRef::Resolved(resolved) => self.resolve_to_primary_namespace(*resolved.declaration_id()),
|
|
1364
1374
|
NameRef::Unresolved(_) => {
|
|
1365
1375
|
// The only case where we wouldn't have the nesting resolved at this point is if it's available through
|
|
1366
1376
|
// inheritance or if it doesn't exist, so we need to retry later
|
|
1367
|
-
Outcome::Retry
|
|
1377
|
+
Outcome::Retry {
|
|
1378
|
+
partial_ancestors: false,
|
|
1379
|
+
}
|
|
1368
1380
|
}
|
|
1369
1381
|
}
|
|
1370
1382
|
} else {
|
|
1371
1383
|
// Any constants at the top level are owned by Object
|
|
1372
|
-
Outcome::Resolved(*OBJECT_ID
|
|
1384
|
+
Outcome::Resolved(*OBJECT_ID)
|
|
1373
1385
|
}
|
|
1374
1386
|
}
|
|
1375
1387
|
|
|
@@ -1390,7 +1402,7 @@ impl<'a> Resolver<'a> {
|
|
|
1390
1402
|
// promotes it correctly. Using nesting would incorrectly create `SomeModule::A`.
|
|
1391
1403
|
let parent_owner_id = if parent_has_parent_scope {
|
|
1392
1404
|
match self.name_owner_id(parent_scope, false) {
|
|
1393
|
-
Outcome::Resolved(id
|
|
1405
|
+
Outcome::Resolved(id) => id,
|
|
1394
1406
|
_ => self.create_todo_for_parent(parent_scope),
|
|
1395
1407
|
}
|
|
1396
1408
|
} else {
|
|
@@ -1398,8 +1410,8 @@ impl<'a> Resolver<'a> {
|
|
|
1398
1410
|
};
|
|
1399
1411
|
|
|
1400
1412
|
// Ensure we follow constant aliases if that's the parent
|
|
1401
|
-
let parent_owner_id = match self.resolve_to_primary_namespace(parent_owner_id
|
|
1402
|
-
Outcome::Resolved(id
|
|
1413
|
+
let parent_owner_id = match self.resolve_to_primary_namespace(parent_owner_id) {
|
|
1414
|
+
Outcome::Resolved(id) => id,
|
|
1403
1415
|
_ => *OBJECT_ID,
|
|
1404
1416
|
};
|
|
1405
1417
|
|
|
@@ -1428,16 +1440,14 @@ impl<'a> Resolver<'a> {
|
|
|
1428
1440
|
|
|
1429
1441
|
/// Resolves a declaration ID through any alias chain to get the primary (first) namespace.
|
|
1430
1442
|
/// Returns `Retry` if the primary alias target hasn't been resolved yet.
|
|
1431
|
-
fn resolve_to_primary_namespace(
|
|
1432
|
-
&self,
|
|
1433
|
-
declaration_id: DeclarationId,
|
|
1434
|
-
linearization: Option<DeclarationId>,
|
|
1435
|
-
) -> Outcome {
|
|
1443
|
+
fn resolve_to_primary_namespace(&self, declaration_id: DeclarationId) -> Outcome {
|
|
1436
1444
|
let resolved_ids = self.resolve_alias_chains(declaration_id);
|
|
1437
1445
|
|
|
1438
1446
|
// Get the primary (first) resolved target
|
|
1439
1447
|
let Some(&primary_id) = resolved_ids.first() else {
|
|
1440
|
-
return Outcome::Retry
|
|
1448
|
+
return Outcome::Retry {
|
|
1449
|
+
partial_ancestors: false,
|
|
1450
|
+
};
|
|
1441
1451
|
};
|
|
1442
1452
|
|
|
1443
1453
|
// Check if the primary result is still an unresolved alias
|
|
@@ -1445,15 +1455,18 @@ impl<'a> Resolver<'a> {
|
|
|
1445
1455
|
self.graph.declarations().get(&primary_id),
|
|
1446
1456
|
Some(Declaration::ConstantAlias(_))
|
|
1447
1457
|
) {
|
|
1448
|
-
return Outcome::Retry
|
|
1458
|
+
return Outcome::Retry {
|
|
1459
|
+
partial_ancestors: false,
|
|
1460
|
+
};
|
|
1449
1461
|
}
|
|
1450
1462
|
|
|
1451
|
-
Outcome::Resolved(primary_id
|
|
1463
|
+
Outcome::Resolved(primary_id)
|
|
1452
1464
|
}
|
|
1453
1465
|
|
|
1454
1466
|
/// Attempts to resolve a constant reference against the graph. Returns the fully qualified declaration ID that the
|
|
1455
1467
|
/// reference is related to or `None`. This method mutates the graph to remember which constants have already been
|
|
1456
1468
|
/// resolved
|
|
1469
|
+
#[allow(clippy::too_many_lines)]
|
|
1457
1470
|
fn resolve_constant_internal(&mut self, name_id: NameId) -> Outcome {
|
|
1458
1471
|
let name_ref = self.graph.names().get(&name_id).unwrap().clone();
|
|
1459
1472
|
|
|
@@ -1463,7 +1476,7 @@ impl<'a> Resolver<'a> {
|
|
|
1463
1476
|
ParentScope::TopLevel => {
|
|
1464
1477
|
let result = self.search_ancestors(*OBJECT_ID, *name.str());
|
|
1465
1478
|
|
|
1466
|
-
if let Outcome::Resolved(declaration_id
|
|
1479
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1467
1480
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1468
1481
|
}
|
|
1469
1482
|
|
|
@@ -1471,7 +1484,9 @@ impl<'a> Resolver<'a> {
|
|
|
1471
1484
|
}
|
|
1472
1485
|
ParentScope::Attached(parent_scope_id) => {
|
|
1473
1486
|
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1474
|
-
return Outcome::Retry
|
|
1487
|
+
return Outcome::Retry {
|
|
1488
|
+
partial_ancestors: false,
|
|
1489
|
+
};
|
|
1475
1490
|
};
|
|
1476
1491
|
|
|
1477
1492
|
let mut target_decl_id = *parent_scope.declaration_id();
|
|
@@ -1485,13 +1500,15 @@ impl<'a> Resolver<'a> {
|
|
|
1485
1500
|
if resolved_ids.iter().any(|id| {
|
|
1486
1501
|
matches!(self.graph.declarations().get(id), Some(Declaration::ConstantAlias(_)))
|
|
1487
1502
|
}) {
|
|
1488
|
-
return Outcome::Retry
|
|
1503
|
+
return Outcome::Retry {
|
|
1504
|
+
partial_ancestors: false,
|
|
1505
|
+
};
|
|
1489
1506
|
}
|
|
1490
1507
|
|
|
1491
1508
|
let Some(&namespace_id) = resolved_ids.iter().find(|id| {
|
|
1492
1509
|
matches!(self.graph.declarations().get(id), Some(Declaration::Namespace(_)))
|
|
1493
1510
|
}) else {
|
|
1494
|
-
return Outcome::Unresolved
|
|
1511
|
+
return Outcome::Unresolved;
|
|
1495
1512
|
};
|
|
1496
1513
|
|
|
1497
1514
|
target_decl_id = namespace_id;
|
|
@@ -1499,17 +1516,21 @@ impl<'a> Resolver<'a> {
|
|
|
1499
1516
|
|
|
1500
1517
|
// If we found a singleton reference with a resolved attached object parent scope, we
|
|
1501
1518
|
// automatically create the singleton class
|
|
1502
|
-
let Some(singleton_id) =
|
|
1503
|
-
|
|
1519
|
+
let Some(singleton_id) =
|
|
1520
|
+
self.get_or_create_singleton_class(target_decl_id, SingletonAncestors::Enqueue)
|
|
1521
|
+
else {
|
|
1522
|
+
return Outcome::Unresolved;
|
|
1504
1523
|
};
|
|
1505
1524
|
self.graph.record_resolved_name(name_id, singleton_id);
|
|
1506
|
-
|
|
1525
|
+
// `get_or_create_singleton_class` already enqueued the singleton's ancestors on
|
|
1526
|
+
// creation, so there is nothing to hand back for re-enqueueing.
|
|
1527
|
+
Outcome::Resolved(singleton_id)
|
|
1507
1528
|
}
|
|
1508
1529
|
ParentScope::None => {
|
|
1509
1530
|
// Otherwise, it's a simple constant read and we can resolve it directly
|
|
1510
1531
|
let result = self.run_resolution(&name);
|
|
1511
1532
|
|
|
1512
|
-
if let Outcome::Resolved(declaration_id
|
|
1533
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1513
1534
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1514
1535
|
}
|
|
1515
1536
|
|
|
@@ -1517,7 +1538,9 @@ impl<'a> Resolver<'a> {
|
|
|
1517
1538
|
}
|
|
1518
1539
|
ParentScope::Some(parent_scope_id) => {
|
|
1519
1540
|
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1520
|
-
return Outcome::Retry
|
|
1541
|
+
return Outcome::Retry {
|
|
1542
|
+
partial_ancestors: false,
|
|
1543
|
+
};
|
|
1521
1544
|
};
|
|
1522
1545
|
|
|
1523
1546
|
// Resolve the namespace in case it's an alias (e.g., ALIAS::CONST where ALIAS = Foo)
|
|
@@ -1525,29 +1548,36 @@ impl<'a> Resolver<'a> {
|
|
|
1525
1548
|
let resolved_ids = self.resolve_alias_chains(*parent_scope.declaration_id());
|
|
1526
1549
|
|
|
1527
1550
|
// Search each resolved target for the constant. Return early if found.
|
|
1528
|
-
let mut
|
|
1551
|
+
let mut missing_partial = false;
|
|
1529
1552
|
let mut found_namespace = false;
|
|
1530
1553
|
|
|
1531
1554
|
for &id in &resolved_ids {
|
|
1532
1555
|
match self.graph.declarations().get(&id) {
|
|
1533
1556
|
Some(Declaration::ConstantAlias(_)) => {
|
|
1534
1557
|
// Alias not fully resolved yet
|
|
1535
|
-
return Outcome::Retry
|
|
1558
|
+
return Outcome::Retry {
|
|
1559
|
+
partial_ancestors: false,
|
|
1560
|
+
};
|
|
1536
1561
|
}
|
|
1537
1562
|
Some(Declaration::Namespace(_)) => {
|
|
1538
1563
|
found_namespace = true;
|
|
1539
1564
|
|
|
1540
1565
|
match self.search_ancestors(id, *name.str()) {
|
|
1541
|
-
Outcome::Resolved(declaration_id
|
|
1566
|
+
Outcome::Resolved(declaration_id) => {
|
|
1542
1567
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1543
|
-
return Outcome::Resolved(declaration_id
|
|
1568
|
+
return Outcome::Resolved(declaration_id);
|
|
1569
|
+
}
|
|
1570
|
+
Outcome::Retry {
|
|
1571
|
+
partial_ancestors: true,
|
|
1572
|
+
} => {
|
|
1573
|
+
missing_partial = true;
|
|
1544
1574
|
}
|
|
1545
|
-
Outcome::
|
|
1546
|
-
|
|
1547
|
-
|
|
1575
|
+
Outcome::Unresolved => {}
|
|
1576
|
+
Outcome::Retry {
|
|
1577
|
+
partial_ancestors: false,
|
|
1578
|
+
} => {
|
|
1579
|
+
unreachable!("search_ancestors never returns a non-partial Retry")
|
|
1548
1580
|
}
|
|
1549
|
-
Outcome::Unresolved(None) => {}
|
|
1550
|
-
Outcome::Retry(_) => unreachable!("search_ancestors never returns Retry"),
|
|
1551
1581
|
}
|
|
1552
1582
|
}
|
|
1553
1583
|
_ => {
|
|
@@ -1558,15 +1588,19 @@ impl<'a> Resolver<'a> {
|
|
|
1558
1588
|
|
|
1559
1589
|
// If no namespaces were found, this constant path can never resolve.
|
|
1560
1590
|
if !found_namespace {
|
|
1561
|
-
return Outcome::Unresolved
|
|
1591
|
+
return Outcome::Unresolved;
|
|
1562
1592
|
}
|
|
1563
1593
|
|
|
1564
|
-
// Member not found in any namespace yet - retry in case it's added later
|
|
1565
|
-
|
|
1594
|
+
// Member not found in any namespace yet - retry in case it's added later. `partial` records
|
|
1595
|
+
// whether the miss was due to a still-partial ancestor chain, so the unit is re-checked once
|
|
1596
|
+
// that chain completes.
|
|
1597
|
+
Outcome::Retry {
|
|
1598
|
+
partial_ancestors: missing_partial,
|
|
1599
|
+
}
|
|
1566
1600
|
}
|
|
1567
1601
|
}
|
|
1568
1602
|
}
|
|
1569
|
-
NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id()
|
|
1603
|
+
NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id()),
|
|
1570
1604
|
}
|
|
1571
1605
|
}
|
|
1572
1606
|
|
|
@@ -1639,13 +1673,15 @@ impl<'a> Resolver<'a> {
|
|
|
1639
1673
|
let (ancestor_outcome, nesting_decl_id) = match self.graph.names().get(nesting).unwrap() {
|
|
1640
1674
|
NameRef::Resolved(nesting_name_ref) => {
|
|
1641
1675
|
let resolved_ids = self.resolve_alias_chains(*nesting_name_ref.declaration_id());
|
|
1642
|
-
let mut result = Outcome::Unresolved
|
|
1676
|
+
let mut result = Outcome::Unresolved;
|
|
1643
1677
|
let mut decl_id = None;
|
|
1644
1678
|
|
|
1645
1679
|
for &id in &resolved_ids {
|
|
1646
1680
|
match self.graph.declarations().get(&id) {
|
|
1647
1681
|
Some(Declaration::ConstantAlias(_)) => {
|
|
1648
|
-
result = Outcome::Retry
|
|
1682
|
+
result = Outcome::Retry {
|
|
1683
|
+
partial_ancestors: false,
|
|
1684
|
+
};
|
|
1649
1685
|
break;
|
|
1650
1686
|
}
|
|
1651
1687
|
Some(Declaration::Namespace(_)) => {
|
|
@@ -1659,7 +1695,12 @@ impl<'a> Resolver<'a> {
|
|
|
1659
1695
|
|
|
1660
1696
|
(result, decl_id)
|
|
1661
1697
|
}
|
|
1662
|
-
NameRef::Unresolved(_) => (
|
|
1698
|
+
NameRef::Unresolved(_) => (
|
|
1699
|
+
Outcome::Retry {
|
|
1700
|
+
partial_ancestors: false,
|
|
1701
|
+
},
|
|
1702
|
+
None,
|
|
1703
|
+
),
|
|
1663
1704
|
};
|
|
1664
1705
|
|
|
1665
1706
|
if matches!(ancestor_outcome, Outcome::Resolved(..)) {
|
|
@@ -1674,18 +1715,18 @@ impl<'a> Resolver<'a> {
|
|
|
1674
1715
|
Some(Declaration::Namespace(Namespace::Module(_) | Namespace::Todo(_)))
|
|
1675
1716
|
)
|
|
1676
1717
|
});
|
|
1677
|
-
let chain_incomplete = matches!(
|
|
1718
|
+
let chain_incomplete = matches!(
|
|
1719
|
+
ancestor_outcome,
|
|
1720
|
+
Outcome::Retry {
|
|
1721
|
+
partial_ancestors: true
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1678
1724
|
|
|
1679
1725
|
if is_module || chain_incomplete {
|
|
1680
1726
|
let object_outcome = self.search_ancestors(*OBJECT_ID, str_id);
|
|
1681
1727
|
|
|
1682
|
-
if let Outcome::Resolved(decl_id
|
|
1683
|
-
|
|
1684
|
-
let linearization_id = match ancestor_outcome {
|
|
1685
|
-
Outcome::Retry(id) | Outcome::Unresolved(id) => id,
|
|
1686
|
-
Outcome::Resolved(..) => unreachable!("guarded by early return above"),
|
|
1687
|
-
};
|
|
1688
|
-
return Outcome::Resolved(decl_id, linearization_id);
|
|
1728
|
+
if let Outcome::Resolved(decl_id) = object_outcome {
|
|
1729
|
+
return Outcome::Resolved(decl_id);
|
|
1689
1730
|
}
|
|
1690
1731
|
}
|
|
1691
1732
|
|
|
@@ -1712,12 +1753,12 @@ impl<'a> Resolver<'a> {
|
|
|
1712
1753
|
.as_namespace()
|
|
1713
1754
|
.unwrap()
|
|
1714
1755
|
.member(&str_id)
|
|
1715
|
-
.map(|id| Outcome::Resolved(*id
|
|
1756
|
+
.map(|id| Outcome::Resolved(*id))
|
|
1716
1757
|
} else {
|
|
1717
1758
|
None
|
|
1718
1759
|
}
|
|
1719
1760
|
})
|
|
1720
|
-
.unwrap_or(Outcome::Unresolved
|
|
1761
|
+
.unwrap_or(Outcome::Unresolved),
|
|
1721
1762
|
Ancestors::Partial(ids) => {
|
|
1722
1763
|
for ancestor_id in ids {
|
|
1723
1764
|
match ancestor_id {
|
|
@@ -1725,7 +1766,9 @@ impl<'a> Resolver<'a> {
|
|
|
1725
1766
|
// Stop at unresolved ancestors to avoid resolving to a later one.
|
|
1726
1767
|
// Skip if the name matches what we're searching for.
|
|
1727
1768
|
if *self.graph.names().get(&name_id).unwrap().str() != str_id {
|
|
1728
|
-
return Outcome::Retry
|
|
1769
|
+
return Outcome::Retry {
|
|
1770
|
+
partial_ancestors: true,
|
|
1771
|
+
};
|
|
1729
1772
|
}
|
|
1730
1773
|
}
|
|
1731
1774
|
Ancestor::Complete(ancestor_id) => {
|
|
@@ -1738,12 +1781,14 @@ impl<'a> Resolver<'a> {
|
|
|
1738
1781
|
.unwrap()
|
|
1739
1782
|
.member(&str_id)
|
|
1740
1783
|
{
|
|
1741
|
-
return Outcome::Resolved(*id
|
|
1784
|
+
return Outcome::Resolved(*id);
|
|
1742
1785
|
}
|
|
1743
1786
|
}
|
|
1744
1787
|
}
|
|
1745
1788
|
}
|
|
1746
|
-
Outcome::
|
|
1789
|
+
Outcome::Retry {
|
|
1790
|
+
partial_ancestors: true,
|
|
1791
|
+
}
|
|
1747
1792
|
}
|
|
1748
1793
|
}
|
|
1749
1794
|
}
|
|
@@ -1760,16 +1805,18 @@ impl<'a> Resolver<'a> {
|
|
|
1760
1805
|
&& let Some(namespace) = self.graph.declarations().get(&namespace_id).unwrap().as_namespace()
|
|
1761
1806
|
&& let Some(member) = namespace.member(&str_id)
|
|
1762
1807
|
{
|
|
1763
|
-
return Outcome::Resolved(*member
|
|
1808
|
+
return Outcome::Resolved(*member);
|
|
1764
1809
|
}
|
|
1765
1810
|
|
|
1766
1811
|
current_name = nesting_name_ref.name();
|
|
1767
1812
|
} else {
|
|
1768
|
-
return Outcome::Retry
|
|
1813
|
+
return Outcome::Retry {
|
|
1814
|
+
partial_ancestors: false,
|
|
1815
|
+
};
|
|
1769
1816
|
}
|
|
1770
1817
|
}
|
|
1771
1818
|
|
|
1772
|
-
Outcome::Unresolved
|
|
1819
|
+
Outcome::Unresolved
|
|
1773
1820
|
}
|
|
1774
1821
|
|
|
1775
1822
|
/// Returns a complexity score for a given name, which is used to sort names for resolution. The complexity is based
|
|
@@ -1980,7 +2027,7 @@ impl<'a> Resolver<'a> {
|
|
|
1980
2027
|
|
|
1981
2028
|
let (inner_parent, partial) = self.singleton_parent_id(owner_id);
|
|
1982
2029
|
(
|
|
1983
|
-
self.get_or_create_singleton_class(inner_parent,
|
|
2030
|
+
self.get_or_create_singleton_class(inner_parent, SingletonAncestors::Deferred)
|
|
1984
2031
|
.expect("singleton parent should always be a namespace"),
|
|
1985
2032
|
partial,
|
|
1986
2033
|
)
|
|
@@ -1991,7 +2038,7 @@ impl<'a> Resolver<'a> {
|
|
|
1991
2038
|
|
|
1992
2039
|
let (picked_parent, unresolved_parent) = self.get_parent_class(&definition_ids);
|
|
1993
2040
|
(
|
|
1994
|
-
self.get_or_create_singleton_class(picked_parent,
|
|
2041
|
+
self.get_or_create_singleton_class(picked_parent, SingletonAncestors::Deferred)
|
|
1995
2042
|
.expect("parent class should always be a namespace"),
|
|
1996
2043
|
unresolved_parent.is_some(),
|
|
1997
2044
|
)
|