rubydex 0.2.7 → 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/ext/rubydex/graph.c +28 -14
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +4 -4
- data/rust/rubydex/src/main.rs +148 -5
- data/rust/rubydex/src/model/declaration.rs +14 -14
- data/rust/rubydex/src/model/graph.rs +85 -2
- 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 +196 -139
- 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 +54 -43
- metadata +2 -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
|
|
@@ -845,19 +853,20 @@ impl<'a> Resolver<'a> {
|
|
|
845
853
|
/// If the declaration is a `Constant` with all-promotable definitions, it is automatically promoted to a `Class`
|
|
846
854
|
/// namespace before creating the singleton. Returns `None` if the declaration is not a namespace and cannot be
|
|
847
855
|
/// promoted (e.g., `FOO = 42`).
|
|
848
|
-
///
|
|
849
|
-
///
|
|
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`].
|
|
850
859
|
fn get_or_create_singleton_class(
|
|
851
860
|
&mut self,
|
|
852
861
|
attached_id: DeclarationId,
|
|
853
|
-
|
|
862
|
+
mode: SingletonAncestors,
|
|
854
863
|
) -> Option<DeclarationId> {
|
|
855
864
|
let attached_decl = self.graph.declarations().get(&attached_id).unwrap();
|
|
856
865
|
|
|
857
866
|
// If the attached object is a constant alias, follow the alias chain to find the actual namespace
|
|
858
867
|
if matches!(attached_decl, Declaration::ConstantAlias(_)) {
|
|
859
868
|
return match self.resolve_to_namespace(attached_id) {
|
|
860
|
-
Some(id) => self.get_or_create_singleton_class(id,
|
|
869
|
+
Some(id) => self.get_or_create_singleton_class(id, mode),
|
|
861
870
|
None => None,
|
|
862
871
|
};
|
|
863
872
|
}
|
|
@@ -868,11 +877,7 @@ impl<'a> Resolver<'a> {
|
|
|
868
877
|
Declaration::Namespace(Namespace::Module(Box::new(ModuleDeclaration::new(name, owner_id))))
|
|
869
878
|
});
|
|
870
879
|
|
|
871
|
-
|
|
872
|
-
let _ = self.ancestors_of(attached_id);
|
|
873
|
-
} else {
|
|
874
|
-
self.unit_queue.push_back(Unit::Ancestors(attached_id));
|
|
875
|
-
}
|
|
880
|
+
self.schedule_singleton_ancestors(attached_id, mode);
|
|
876
881
|
} else {
|
|
877
882
|
return None;
|
|
878
883
|
}
|
|
@@ -900,15 +905,26 @@ impl<'a> Resolver<'a> {
|
|
|
900
905
|
)))),
|
|
901
906
|
);
|
|
902
907
|
|
|
903
|
-
|
|
904
|
-
let _ = self.ancestors_of(decl_id);
|
|
905
|
-
} else {
|
|
906
|
-
self.unit_queue.push_back(Unit::Ancestors(decl_id));
|
|
907
|
-
}
|
|
908
|
+
self.schedule_singleton_ancestors(decl_id, mode);
|
|
908
909
|
|
|
909
910
|
Some(decl_id)
|
|
910
911
|
}
|
|
911
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
|
+
|
|
912
928
|
/// Linearizes the ancestors of a declaration, returning the list of ancestor declaration IDs
|
|
913
929
|
///
|
|
914
930
|
/// # Panics
|
|
@@ -1011,7 +1027,7 @@ impl<'a> Resolver<'a> {
|
|
|
1011
1027
|
|
|
1012
1028
|
// Ensure that we create the singleton and enqueue it for linearization if we see an extend
|
|
1013
1029
|
if has_extends && !is_singleton_class {
|
|
1014
|
-
self.get_or_create_singleton_class(declaration_id,
|
|
1030
|
+
self.get_or_create_singleton_class(declaration_id, SingletonAncestors::Enqueue);
|
|
1015
1031
|
}
|
|
1016
1032
|
|
|
1017
1033
|
let (linearized_prepends, linearized_includes) =
|
|
@@ -1251,15 +1267,17 @@ impl<'a> Resolver<'a> {
|
|
|
1251
1267
|
// `set_singleton_class_id`, not `add_member`, so a TODO receiver would never gain a
|
|
1252
1268
|
// member. Emit Retry so the unit is preserved for a later resolve where the receiver
|
|
1253
1269
|
// may exist.
|
|
1254
|
-
Outcome::Unresolved
|
|
1255
|
-
|
|
1270
|
+
Outcome::Unresolved if singleton => Outcome::Retry {
|
|
1271
|
+
partial_ancestors: false,
|
|
1272
|
+
},
|
|
1273
|
+
Outcome::Unresolved => Outcome::Resolved(self.create_todo_for_parent(name_id)),
|
|
1256
1274
|
other => other,
|
|
1257
1275
|
};
|
|
1258
1276
|
|
|
1259
1277
|
// The name of the declaration is determined by the name of its owner, which may or may not require resolution
|
|
1260
1278
|
// depending on whether the name has a parent scope
|
|
1261
1279
|
match outcome {
|
|
1262
|
-
Outcome::Resolved(owner_id
|
|
1280
|
+
Outcome::Resolved(owner_id) => {
|
|
1263
1281
|
let mut fully_qualified_name = self.graph.strings().get(&str_id).unwrap().to_string();
|
|
1264
1282
|
|
|
1265
1283
|
// If the owner is a promotable constant and something is being defined inside it, promote it to a
|
|
@@ -1284,7 +1302,7 @@ impl<'a> Resolver<'a> {
|
|
|
1284
1302
|
// Foo = 1
|
|
1285
1303
|
// class << Foo; end
|
|
1286
1304
|
if singleton && !owner_is_namespace {
|
|
1287
|
-
return Outcome::Unresolved
|
|
1305
|
+
return Outcome::Unresolved;
|
|
1288
1306
|
}
|
|
1289
1307
|
|
|
1290
1308
|
// We don't prefix declarations with `Object::`
|
|
@@ -1314,7 +1332,7 @@ impl<'a> Resolver<'a> {
|
|
|
1314
1332
|
}
|
|
1315
1333
|
|
|
1316
1334
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1317
|
-
Outcome::Resolved(declaration_id
|
|
1335
|
+
Outcome::Resolved(declaration_id)
|
|
1318
1336
|
}
|
|
1319
1337
|
other => other,
|
|
1320
1338
|
}
|
|
@@ -1334,12 +1352,14 @@ impl<'a> Resolver<'a> {
|
|
|
1334
1352
|
// If we have `A::B`, the owner of `B` is whatever `A` resolves to.
|
|
1335
1353
|
// If `A` is an alias, resolve through to get the actual namespace.
|
|
1336
1354
|
match self.resolve_constant_internal(parent_scope) {
|
|
1337
|
-
Outcome::Resolved(id
|
|
1355
|
+
Outcome::Resolved(id) => self.resolve_to_primary_namespace(id),
|
|
1338
1356
|
// The parent scope is genuinely unknown — not a circular alias or pending
|
|
1339
1357
|
// linearization, but a name that doesn't exist anywhere in the graph.
|
|
1340
|
-
Outcome::Unresolved
|
|
1341
|
-
Outcome::Retry
|
|
1342
|
-
|
|
1358
|
+
Outcome::Unresolved => Outcome::Unresolved,
|
|
1359
|
+
Outcome::Retry {
|
|
1360
|
+
partial_ancestors: false,
|
|
1361
|
+
} if !preserve_retry => Outcome::Unresolved,
|
|
1362
|
+
retry @ Outcome::Retry { .. } => retry,
|
|
1343
1363
|
}
|
|
1344
1364
|
} else if let Some(nesting_id) = name_ref.nesting()
|
|
1345
1365
|
&& !name_ref.parent_scope().is_top_level()
|
|
@@ -1350,16 +1370,18 @@ impl<'a> Resolver<'a> {
|
|
|
1350
1370
|
// end
|
|
1351
1371
|
// If `ALIAS` points to `Outer`, `CONST` should be owned by `Outer::Target`, not `ALIAS::Target`.
|
|
1352
1372
|
match self.graph.names().get(nesting_id).unwrap() {
|
|
1353
|
-
NameRef::Resolved(resolved) => self.resolve_to_primary_namespace(*resolved.declaration_id()
|
|
1373
|
+
NameRef::Resolved(resolved) => self.resolve_to_primary_namespace(*resolved.declaration_id()),
|
|
1354
1374
|
NameRef::Unresolved(_) => {
|
|
1355
1375
|
// The only case where we wouldn't have the nesting resolved at this point is if it's available through
|
|
1356
1376
|
// inheritance or if it doesn't exist, so we need to retry later
|
|
1357
|
-
Outcome::Retry
|
|
1377
|
+
Outcome::Retry {
|
|
1378
|
+
partial_ancestors: false,
|
|
1379
|
+
}
|
|
1358
1380
|
}
|
|
1359
1381
|
}
|
|
1360
1382
|
} else {
|
|
1361
1383
|
// Any constants at the top level are owned by Object
|
|
1362
|
-
Outcome::Resolved(*OBJECT_ID
|
|
1384
|
+
Outcome::Resolved(*OBJECT_ID)
|
|
1363
1385
|
}
|
|
1364
1386
|
}
|
|
1365
1387
|
|
|
@@ -1380,7 +1402,7 @@ impl<'a> Resolver<'a> {
|
|
|
1380
1402
|
// promotes it correctly. Using nesting would incorrectly create `SomeModule::A`.
|
|
1381
1403
|
let parent_owner_id = if parent_has_parent_scope {
|
|
1382
1404
|
match self.name_owner_id(parent_scope, false) {
|
|
1383
|
-
Outcome::Resolved(id
|
|
1405
|
+
Outcome::Resolved(id) => id,
|
|
1384
1406
|
_ => self.create_todo_for_parent(parent_scope),
|
|
1385
1407
|
}
|
|
1386
1408
|
} else {
|
|
@@ -1388,8 +1410,8 @@ impl<'a> Resolver<'a> {
|
|
|
1388
1410
|
};
|
|
1389
1411
|
|
|
1390
1412
|
// Ensure we follow constant aliases if that's the parent
|
|
1391
|
-
let parent_owner_id = match self.resolve_to_primary_namespace(parent_owner_id
|
|
1392
|
-
Outcome::Resolved(id
|
|
1413
|
+
let parent_owner_id = match self.resolve_to_primary_namespace(parent_owner_id) {
|
|
1414
|
+
Outcome::Resolved(id) => id,
|
|
1393
1415
|
_ => *OBJECT_ID,
|
|
1394
1416
|
};
|
|
1395
1417
|
|
|
@@ -1418,16 +1440,14 @@ impl<'a> Resolver<'a> {
|
|
|
1418
1440
|
|
|
1419
1441
|
/// Resolves a declaration ID through any alias chain to get the primary (first) namespace.
|
|
1420
1442
|
/// Returns `Retry` if the primary alias target hasn't been resolved yet.
|
|
1421
|
-
fn resolve_to_primary_namespace(
|
|
1422
|
-
&self,
|
|
1423
|
-
declaration_id: DeclarationId,
|
|
1424
|
-
linearization: Option<DeclarationId>,
|
|
1425
|
-
) -> Outcome {
|
|
1443
|
+
fn resolve_to_primary_namespace(&self, declaration_id: DeclarationId) -> Outcome {
|
|
1426
1444
|
let resolved_ids = self.resolve_alias_chains(declaration_id);
|
|
1427
1445
|
|
|
1428
1446
|
// Get the primary (first) resolved target
|
|
1429
1447
|
let Some(&primary_id) = resolved_ids.first() else {
|
|
1430
|
-
return Outcome::Retry
|
|
1448
|
+
return Outcome::Retry {
|
|
1449
|
+
partial_ancestors: false,
|
|
1450
|
+
};
|
|
1431
1451
|
};
|
|
1432
1452
|
|
|
1433
1453
|
// Check if the primary result is still an unresolved alias
|
|
@@ -1435,15 +1455,18 @@ impl<'a> Resolver<'a> {
|
|
|
1435
1455
|
self.graph.declarations().get(&primary_id),
|
|
1436
1456
|
Some(Declaration::ConstantAlias(_))
|
|
1437
1457
|
) {
|
|
1438
|
-
return Outcome::Retry
|
|
1458
|
+
return Outcome::Retry {
|
|
1459
|
+
partial_ancestors: false,
|
|
1460
|
+
};
|
|
1439
1461
|
}
|
|
1440
1462
|
|
|
1441
|
-
Outcome::Resolved(primary_id
|
|
1463
|
+
Outcome::Resolved(primary_id)
|
|
1442
1464
|
}
|
|
1443
1465
|
|
|
1444
1466
|
/// Attempts to resolve a constant reference against the graph. Returns the fully qualified declaration ID that the
|
|
1445
1467
|
/// reference is related to or `None`. This method mutates the graph to remember which constants have already been
|
|
1446
1468
|
/// resolved
|
|
1469
|
+
#[allow(clippy::too_many_lines)]
|
|
1447
1470
|
fn resolve_constant_internal(&mut self, name_id: NameId) -> Outcome {
|
|
1448
1471
|
let name_ref = self.graph.names().get(&name_id).unwrap().clone();
|
|
1449
1472
|
|
|
@@ -1453,7 +1476,7 @@ impl<'a> Resolver<'a> {
|
|
|
1453
1476
|
ParentScope::TopLevel => {
|
|
1454
1477
|
let result = self.search_ancestors(*OBJECT_ID, *name.str());
|
|
1455
1478
|
|
|
1456
|
-
if let Outcome::Resolved(declaration_id
|
|
1479
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1457
1480
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1458
1481
|
}
|
|
1459
1482
|
|
|
@@ -1461,7 +1484,9 @@ impl<'a> Resolver<'a> {
|
|
|
1461
1484
|
}
|
|
1462
1485
|
ParentScope::Attached(parent_scope_id) => {
|
|
1463
1486
|
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1464
|
-
return Outcome::Retry
|
|
1487
|
+
return Outcome::Retry {
|
|
1488
|
+
partial_ancestors: false,
|
|
1489
|
+
};
|
|
1465
1490
|
};
|
|
1466
1491
|
|
|
1467
1492
|
let mut target_decl_id = *parent_scope.declaration_id();
|
|
@@ -1475,13 +1500,15 @@ impl<'a> Resolver<'a> {
|
|
|
1475
1500
|
if resolved_ids.iter().any(|id| {
|
|
1476
1501
|
matches!(self.graph.declarations().get(id), Some(Declaration::ConstantAlias(_)))
|
|
1477
1502
|
}) {
|
|
1478
|
-
return Outcome::Retry
|
|
1503
|
+
return Outcome::Retry {
|
|
1504
|
+
partial_ancestors: false,
|
|
1505
|
+
};
|
|
1479
1506
|
}
|
|
1480
1507
|
|
|
1481
1508
|
let Some(&namespace_id) = resolved_ids.iter().find(|id| {
|
|
1482
1509
|
matches!(self.graph.declarations().get(id), Some(Declaration::Namespace(_)))
|
|
1483
1510
|
}) else {
|
|
1484
|
-
return Outcome::Unresolved
|
|
1511
|
+
return Outcome::Unresolved;
|
|
1485
1512
|
};
|
|
1486
1513
|
|
|
1487
1514
|
target_decl_id = namespace_id;
|
|
@@ -1489,17 +1516,21 @@ impl<'a> Resolver<'a> {
|
|
|
1489
1516
|
|
|
1490
1517
|
// If we found a singleton reference with a resolved attached object parent scope, we
|
|
1491
1518
|
// automatically create the singleton class
|
|
1492
|
-
let Some(singleton_id) =
|
|
1493
|
-
|
|
1519
|
+
let Some(singleton_id) =
|
|
1520
|
+
self.get_or_create_singleton_class(target_decl_id, SingletonAncestors::Enqueue)
|
|
1521
|
+
else {
|
|
1522
|
+
return Outcome::Unresolved;
|
|
1494
1523
|
};
|
|
1495
1524
|
self.graph.record_resolved_name(name_id, singleton_id);
|
|
1496
|
-
|
|
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)
|
|
1497
1528
|
}
|
|
1498
1529
|
ParentScope::None => {
|
|
1499
1530
|
// Otherwise, it's a simple constant read and we can resolve it directly
|
|
1500
1531
|
let result = self.run_resolution(&name);
|
|
1501
1532
|
|
|
1502
|
-
if let Outcome::Resolved(declaration_id
|
|
1533
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1503
1534
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1504
1535
|
}
|
|
1505
1536
|
|
|
@@ -1507,7 +1538,9 @@ impl<'a> Resolver<'a> {
|
|
|
1507
1538
|
}
|
|
1508
1539
|
ParentScope::Some(parent_scope_id) => {
|
|
1509
1540
|
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1510
|
-
return Outcome::Retry
|
|
1541
|
+
return Outcome::Retry {
|
|
1542
|
+
partial_ancestors: false,
|
|
1543
|
+
};
|
|
1511
1544
|
};
|
|
1512
1545
|
|
|
1513
1546
|
// Resolve the namespace in case it's an alias (e.g., ALIAS::CONST where ALIAS = Foo)
|
|
@@ -1515,29 +1548,36 @@ impl<'a> Resolver<'a> {
|
|
|
1515
1548
|
let resolved_ids = self.resolve_alias_chains(*parent_scope.declaration_id());
|
|
1516
1549
|
|
|
1517
1550
|
// Search each resolved target for the constant. Return early if found.
|
|
1518
|
-
let mut
|
|
1551
|
+
let mut missing_partial = false;
|
|
1519
1552
|
let mut found_namespace = false;
|
|
1520
1553
|
|
|
1521
1554
|
for &id in &resolved_ids {
|
|
1522
1555
|
match self.graph.declarations().get(&id) {
|
|
1523
1556
|
Some(Declaration::ConstantAlias(_)) => {
|
|
1524
1557
|
// Alias not fully resolved yet
|
|
1525
|
-
return Outcome::Retry
|
|
1558
|
+
return Outcome::Retry {
|
|
1559
|
+
partial_ancestors: false,
|
|
1560
|
+
};
|
|
1526
1561
|
}
|
|
1527
1562
|
Some(Declaration::Namespace(_)) => {
|
|
1528
1563
|
found_namespace = true;
|
|
1529
1564
|
|
|
1530
1565
|
match self.search_ancestors(id, *name.str()) {
|
|
1531
|
-
Outcome::Resolved(declaration_id
|
|
1566
|
+
Outcome::Resolved(declaration_id) => {
|
|
1532
1567
|
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1533
|
-
return Outcome::Resolved(declaration_id
|
|
1568
|
+
return Outcome::Resolved(declaration_id);
|
|
1569
|
+
}
|
|
1570
|
+
Outcome::Retry {
|
|
1571
|
+
partial_ancestors: true,
|
|
1572
|
+
} => {
|
|
1573
|
+
missing_partial = true;
|
|
1534
1574
|
}
|
|
1535
|
-
Outcome::
|
|
1536
|
-
|
|
1537
|
-
|
|
1575
|
+
Outcome::Unresolved => {}
|
|
1576
|
+
Outcome::Retry {
|
|
1577
|
+
partial_ancestors: false,
|
|
1578
|
+
} => {
|
|
1579
|
+
unreachable!("search_ancestors never returns a non-partial Retry")
|
|
1538
1580
|
}
|
|
1539
|
-
Outcome::Unresolved(None) => {}
|
|
1540
|
-
Outcome::Retry(_) => unreachable!("search_ancestors never returns Retry"),
|
|
1541
1581
|
}
|
|
1542
1582
|
}
|
|
1543
1583
|
_ => {
|
|
@@ -1548,15 +1588,19 @@ impl<'a> Resolver<'a> {
|
|
|
1548
1588
|
|
|
1549
1589
|
// If no namespaces were found, this constant path can never resolve.
|
|
1550
1590
|
if !found_namespace {
|
|
1551
|
-
return Outcome::Unresolved
|
|
1591
|
+
return Outcome::Unresolved;
|
|
1552
1592
|
}
|
|
1553
1593
|
|
|
1554
|
-
// Member not found in any namespace yet - retry in case it's added later
|
|
1555
|
-
|
|
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
|
+
}
|
|
1556
1600
|
}
|
|
1557
1601
|
}
|
|
1558
1602
|
}
|
|
1559
|
-
NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id()
|
|
1603
|
+
NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id()),
|
|
1560
1604
|
}
|
|
1561
1605
|
}
|
|
1562
1606
|
|
|
@@ -1629,13 +1673,15 @@ impl<'a> Resolver<'a> {
|
|
|
1629
1673
|
let (ancestor_outcome, nesting_decl_id) = match self.graph.names().get(nesting).unwrap() {
|
|
1630
1674
|
NameRef::Resolved(nesting_name_ref) => {
|
|
1631
1675
|
let resolved_ids = self.resolve_alias_chains(*nesting_name_ref.declaration_id());
|
|
1632
|
-
let mut result = Outcome::Unresolved
|
|
1676
|
+
let mut result = Outcome::Unresolved;
|
|
1633
1677
|
let mut decl_id = None;
|
|
1634
1678
|
|
|
1635
1679
|
for &id in &resolved_ids {
|
|
1636
1680
|
match self.graph.declarations().get(&id) {
|
|
1637
1681
|
Some(Declaration::ConstantAlias(_)) => {
|
|
1638
|
-
result = Outcome::Retry
|
|
1682
|
+
result = Outcome::Retry {
|
|
1683
|
+
partial_ancestors: false,
|
|
1684
|
+
};
|
|
1639
1685
|
break;
|
|
1640
1686
|
}
|
|
1641
1687
|
Some(Declaration::Namespace(_)) => {
|
|
@@ -1649,7 +1695,12 @@ impl<'a> Resolver<'a> {
|
|
|
1649
1695
|
|
|
1650
1696
|
(result, decl_id)
|
|
1651
1697
|
}
|
|
1652
|
-
NameRef::Unresolved(_) => (
|
|
1698
|
+
NameRef::Unresolved(_) => (
|
|
1699
|
+
Outcome::Retry {
|
|
1700
|
+
partial_ancestors: false,
|
|
1701
|
+
},
|
|
1702
|
+
None,
|
|
1703
|
+
),
|
|
1653
1704
|
};
|
|
1654
1705
|
|
|
1655
1706
|
if matches!(ancestor_outcome, Outcome::Resolved(..)) {
|
|
@@ -1664,18 +1715,18 @@ impl<'a> Resolver<'a> {
|
|
|
1664
1715
|
Some(Declaration::Namespace(Namespace::Module(_) | Namespace::Todo(_)))
|
|
1665
1716
|
)
|
|
1666
1717
|
});
|
|
1667
|
-
let chain_incomplete = matches!(
|
|
1718
|
+
let chain_incomplete = matches!(
|
|
1719
|
+
ancestor_outcome,
|
|
1720
|
+
Outcome::Retry {
|
|
1721
|
+
partial_ancestors: true
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1668
1724
|
|
|
1669
1725
|
if is_module || chain_incomplete {
|
|
1670
1726
|
let object_outcome = self.search_ancestors(*OBJECT_ID, str_id);
|
|
1671
1727
|
|
|
1672
|
-
if let Outcome::Resolved(decl_id
|
|
1673
|
-
|
|
1674
|
-
let linearization_id = match ancestor_outcome {
|
|
1675
|
-
Outcome::Retry(id) | Outcome::Unresolved(id) => id,
|
|
1676
|
-
Outcome::Resolved(..) => unreachable!("guarded by early return above"),
|
|
1677
|
-
};
|
|
1678
|
-
return Outcome::Resolved(decl_id, linearization_id);
|
|
1728
|
+
if let Outcome::Resolved(decl_id) = object_outcome {
|
|
1729
|
+
return Outcome::Resolved(decl_id);
|
|
1679
1730
|
}
|
|
1680
1731
|
}
|
|
1681
1732
|
|
|
@@ -1702,12 +1753,12 @@ impl<'a> Resolver<'a> {
|
|
|
1702
1753
|
.as_namespace()
|
|
1703
1754
|
.unwrap()
|
|
1704
1755
|
.member(&str_id)
|
|
1705
|
-
.map(|id| Outcome::Resolved(*id
|
|
1756
|
+
.map(|id| Outcome::Resolved(*id))
|
|
1706
1757
|
} else {
|
|
1707
1758
|
None
|
|
1708
1759
|
}
|
|
1709
1760
|
})
|
|
1710
|
-
.unwrap_or(Outcome::Unresolved
|
|
1761
|
+
.unwrap_or(Outcome::Unresolved),
|
|
1711
1762
|
Ancestors::Partial(ids) => {
|
|
1712
1763
|
for ancestor_id in ids {
|
|
1713
1764
|
match ancestor_id {
|
|
@@ -1715,7 +1766,9 @@ impl<'a> Resolver<'a> {
|
|
|
1715
1766
|
// Stop at unresolved ancestors to avoid resolving to a later one.
|
|
1716
1767
|
// Skip if the name matches what we're searching for.
|
|
1717
1768
|
if *self.graph.names().get(&name_id).unwrap().str() != str_id {
|
|
1718
|
-
return Outcome::Retry
|
|
1769
|
+
return Outcome::Retry {
|
|
1770
|
+
partial_ancestors: true,
|
|
1771
|
+
};
|
|
1719
1772
|
}
|
|
1720
1773
|
}
|
|
1721
1774
|
Ancestor::Complete(ancestor_id) => {
|
|
@@ -1728,12 +1781,14 @@ impl<'a> Resolver<'a> {
|
|
|
1728
1781
|
.unwrap()
|
|
1729
1782
|
.member(&str_id)
|
|
1730
1783
|
{
|
|
1731
|
-
return Outcome::Resolved(*id
|
|
1784
|
+
return Outcome::Resolved(*id);
|
|
1732
1785
|
}
|
|
1733
1786
|
}
|
|
1734
1787
|
}
|
|
1735
1788
|
}
|
|
1736
|
-
Outcome::
|
|
1789
|
+
Outcome::Retry {
|
|
1790
|
+
partial_ancestors: true,
|
|
1791
|
+
}
|
|
1737
1792
|
}
|
|
1738
1793
|
}
|
|
1739
1794
|
}
|
|
@@ -1750,16 +1805,18 @@ impl<'a> Resolver<'a> {
|
|
|
1750
1805
|
&& let Some(namespace) = self.graph.declarations().get(&namespace_id).unwrap().as_namespace()
|
|
1751
1806
|
&& let Some(member) = namespace.member(&str_id)
|
|
1752
1807
|
{
|
|
1753
|
-
return Outcome::Resolved(*member
|
|
1808
|
+
return Outcome::Resolved(*member);
|
|
1754
1809
|
}
|
|
1755
1810
|
|
|
1756
1811
|
current_name = nesting_name_ref.name();
|
|
1757
1812
|
} else {
|
|
1758
|
-
return Outcome::Retry
|
|
1813
|
+
return Outcome::Retry {
|
|
1814
|
+
partial_ancestors: false,
|
|
1815
|
+
};
|
|
1759
1816
|
}
|
|
1760
1817
|
}
|
|
1761
1818
|
|
|
1762
|
-
Outcome::Unresolved
|
|
1819
|
+
Outcome::Unresolved
|
|
1763
1820
|
}
|
|
1764
1821
|
|
|
1765
1822
|
/// Returns a complexity score for a given name, which is used to sort names for resolution. The complexity is based
|
|
@@ -1970,7 +2027,7 @@ impl<'a> Resolver<'a> {
|
|
|
1970
2027
|
|
|
1971
2028
|
let (inner_parent, partial) = self.singleton_parent_id(owner_id);
|
|
1972
2029
|
(
|
|
1973
|
-
self.get_or_create_singleton_class(inner_parent,
|
|
2030
|
+
self.get_or_create_singleton_class(inner_parent, SingletonAncestors::Deferred)
|
|
1974
2031
|
.expect("singleton parent should always be a namespace"),
|
|
1975
2032
|
partial,
|
|
1976
2033
|
)
|
|
@@ -1981,7 +2038,7 @@ impl<'a> Resolver<'a> {
|
|
|
1981
2038
|
|
|
1982
2039
|
let (picked_parent, unresolved_parent) = self.get_parent_class(&definition_ids);
|
|
1983
2040
|
(
|
|
1984
|
-
self.get_or_create_singleton_class(picked_parent,
|
|
2041
|
+
self.get_or_create_singleton_class(picked_parent, SingletonAncestors::Deferred)
|
|
1985
2042
|
.expect("parent class should always be a namespace"),
|
|
1986
2043
|
unresolved_parent.is_some(),
|
|
1987
2044
|
)
|