rubydex 0.2.9 → 0.3.0
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/README.md +69 -3
- data/THIRD_PARTY_LICENSES.html +34 -1
- data/exe/rdx +131 -55
- data/ext/rubydex/declaration.c +1 -1
- data/ext/rubydex/definition.c +32 -4
- data/ext/rubydex/graph.c +14 -3
- data/ext/rubydex/query.c +105 -0
- data/ext/rubydex/query.h +8 -0
- data/ext/rubydex/reference.c +60 -0
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +12 -0
- data/ext/rubydex/utils.h +5 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +22 -0
- data/rust/Cargo.lock +7 -0
- data/rust/rubydex/Cargo.toml +1 -0
- data/rust/rubydex/benches/graph_memory.rs +22 -4
- data/rust/rubydex/src/compile_assertions.rs +15 -0
- data/rust/rubydex/src/diagnostic.rs +1 -1
- data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
- data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
- data/rust/rubydex/src/main.rs +2 -124
- data/rust/rubydex/src/model/declaration.rs +0 -11
- data/rust/rubydex/src/model/definitions.rs +27 -26
- data/rust/rubydex/src/model/document.rs +43 -7
- data/rust/rubydex/src/model/graph.rs +40 -28
- data/rust/rubydex/src/model/id.rs +55 -0
- data/rust/rubydex/src/model/ids.rs +21 -9
- data/rust/rubydex/src/model/name.rs +35 -7
- data/rust/rubydex/src/model/references.rs +16 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
- data/rust/rubydex/src/query/cypher/schema.rs +790 -0
- data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
- data/rust/rubydex/src/query/cypher/tests.rs +228 -0
- data/rust/rubydex/src/query/cypher.rs +57 -0
- data/rust/rubydex/src/query.rs +2 -0
- data/rust/rubydex/src/resolution.rs +248 -227
- data/rust/rubydex/src/resolution_tests.rs +263 -65
- data/rust/rubydex-sys/src/declaration_api.rs +6 -3
- data/rust/rubydex-sys/src/definition_api.rs +27 -7
- data/rust/rubydex-sys/src/graph_api.rs +158 -14
- data/rust/rubydex-sys/src/reference_api.rs +58 -12
- metadata +8 -2
|
@@ -14,8 +14,9 @@ use crate::model::{
|
|
|
14
14
|
definitions::{Definition, Mixin, Receiver},
|
|
15
15
|
graph::{Graph, Unit},
|
|
16
16
|
identity_maps::{IdentityHashBuilder, IdentityHashMap, IdentityHashSet},
|
|
17
|
-
ids::{ConstantReferenceId, DeclarationId, DefinitionId, NameId, StringId},
|
|
17
|
+
ids::{ConstantReferenceId, DeclarationId, DefinitionId, NameId, StringId, UriId},
|
|
18
18
|
name::{Name, NameRef, ParentScope},
|
|
19
|
+
visibility::Visibility,
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
enum Outcome {
|
|
@@ -635,7 +636,7 @@ impl<'a> Resolver<'a> {
|
|
|
635
636
|
}
|
|
636
637
|
|
|
637
638
|
/// Resolves retroactive method visibility changes (`private :foo`, `protected :foo`, `public :foo`,
|
|
638
|
-
/// `private_class_method :foo`, `public_class_method :foo`).
|
|
639
|
+
/// `private_class_method :foo`, `public_class_method :foo`, `module_function :foo`).
|
|
639
640
|
///
|
|
640
641
|
/// Runs as a second pass after all methods/attrs are declared, so `private :bar` works
|
|
641
642
|
/// regardless of whether `def bar` appeared before or after it in source.
|
|
@@ -652,12 +653,14 @@ impl<'a> Resolver<'a> {
|
|
|
652
653
|
let offset = method_visibility.offset().clone();
|
|
653
654
|
let lexical_nesting_id = *method_visibility.lexical_nesting_id();
|
|
654
655
|
let is_singleton = method_visibility.flags().is_singleton_method_visibility();
|
|
656
|
+
let is_module_function = *method_visibility.visibility() == Visibility::ModuleFunction;
|
|
657
|
+
let is_module_function_singleton = is_singleton && is_module_function;
|
|
655
658
|
|
|
656
659
|
let Some(lexical_owner_id) = self.resolve_lexical_owner(lexical_nesting_id, id) else {
|
|
657
660
|
continue;
|
|
658
661
|
};
|
|
659
662
|
|
|
660
|
-
let
|
|
663
|
+
let namespace_id = if is_singleton && !is_module_function {
|
|
661
664
|
let Some(singleton_id) =
|
|
662
665
|
self.get_or_create_singleton_class(lexical_owner_id, SingletonAncestors::Eager)
|
|
663
666
|
else {
|
|
@@ -668,12 +671,12 @@ impl<'a> Resolver<'a> {
|
|
|
668
671
|
lexical_owner_id
|
|
669
672
|
};
|
|
670
673
|
|
|
671
|
-
let Some(Declaration::Namespace(namespace)) = self.graph.declarations().get(&
|
|
674
|
+
let Some(Declaration::Namespace(namespace)) = self.graph.declarations().get(&namespace_id) else {
|
|
672
675
|
continue;
|
|
673
676
|
};
|
|
674
677
|
|
|
675
|
-
let mut
|
|
676
|
-
let mut
|
|
678
|
+
let mut visibility_definition_attached = false;
|
|
679
|
+
let mut ancestor_chain_is_incomplete = false;
|
|
677
680
|
|
|
678
681
|
for ancestor in namespace.ancestors() {
|
|
679
682
|
match ancestor {
|
|
@@ -687,40 +690,51 @@ impl<'a> Resolver<'a> {
|
|
|
687
690
|
.is_some();
|
|
688
691
|
|
|
689
692
|
if has_member {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
693
|
+
if is_module_function_singleton {
|
|
694
|
+
let Some(singleton_id) =
|
|
695
|
+
self.get_or_create_singleton_class(lexical_owner_id, SingletonAncestors::Eager)
|
|
696
|
+
else {
|
|
697
|
+
break;
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
self.create_declaration(str_id, id, singleton_id, |name| {
|
|
701
|
+
Declaration::Method(Box::new(MethodDeclaration::new(name, singleton_id)))
|
|
702
|
+
});
|
|
703
|
+
} else {
|
|
704
|
+
self.create_declaration(str_id, id, namespace_id, |name| {
|
|
705
|
+
Declaration::Method(Box::new(MethodDeclaration::new(name, namespace_id)))
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
visibility_definition_attached = true;
|
|
697
709
|
break;
|
|
698
710
|
}
|
|
699
711
|
}
|
|
700
|
-
Ancestor::Partial(_) =>
|
|
712
|
+
Ancestor::Partial(_) => ancestor_chain_is_incomplete = true,
|
|
701
713
|
}
|
|
702
714
|
}
|
|
703
715
|
|
|
704
|
-
if
|
|
716
|
+
if visibility_definition_attached {
|
|
705
717
|
continue;
|
|
706
718
|
}
|
|
707
719
|
|
|
708
|
-
if
|
|
709
|
-
// Method might exist on an unresolved ancestor — requeue for retry.
|
|
720
|
+
if ancestor_chain_is_incomplete {
|
|
710
721
|
pending_work.push(Unit::Definition(id));
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
Rule::UndefinedMethodVisibilityTarget,
|
|
717
|
-
uri_id,
|
|
718
|
-
offset,
|
|
719
|
-
format!("undefined method `{owner_name}#{method_name}` for visibility change"),
|
|
720
|
-
);
|
|
721
|
-
|
|
722
|
-
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if is_module_function_singleton {
|
|
726
|
+
continue;
|
|
723
727
|
}
|
|
728
|
+
|
|
729
|
+
let method_name = self.graph.strings().get(&str_id).unwrap().as_str().to_string();
|
|
730
|
+
let owner_name = self.graph.declarations().get(&namespace_id).unwrap().name().to_string();
|
|
731
|
+
let diagnostic = Diagnostic::new(
|
|
732
|
+
Rule::UndefinedMethodVisibilityTarget,
|
|
733
|
+
uri_id,
|
|
734
|
+
offset,
|
|
735
|
+
format!("undefined method `{owner_name}#{method_name}` for visibility change"),
|
|
736
|
+
);
|
|
737
|
+
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
724
738
|
}
|
|
725
739
|
|
|
726
740
|
// Must extend work here so incremental resolution can resolve previously unresolved visibility operations
|
|
@@ -850,7 +864,7 @@ impl<'a> Resolver<'a> {
|
|
|
850
864
|
/// Gets or creates a singleton class declaration for a given class/module declaration. For class `Foo`, this
|
|
851
865
|
/// returns the declaration for `Foo::<Foo>`.
|
|
852
866
|
///
|
|
853
|
-
/// If the declaration is a `Constant` with all-promotable definitions, it is automatically promoted to
|
|
867
|
+
/// If the declaration is a `Constant` with all-promotable definitions, it is automatically promoted to an unknown
|
|
854
868
|
/// namespace before creating the singleton. Returns `None` if the declaration is not a namespace and cannot be
|
|
855
869
|
/// promoted (e.g., `FOO = 42`).
|
|
856
870
|
/// `ancestors` controls how the singleton's ancestor chain is scheduled for linearization —
|
|
@@ -863,6 +877,10 @@ impl<'a> Resolver<'a> {
|
|
|
863
877
|
) -> Option<DeclarationId> {
|
|
864
878
|
let attached_decl = self.graph.declarations().get(&attached_id).unwrap();
|
|
865
879
|
|
|
880
|
+
if let Some(singleton_id) = attached_decl.as_namespace().and_then(|d| d.singleton_class()) {
|
|
881
|
+
return Some(*singleton_id);
|
|
882
|
+
}
|
|
883
|
+
|
|
866
884
|
// If the attached object is a constant alias, follow the alias chain to find the actual namespace
|
|
867
885
|
if matches!(attached_decl, Declaration::ConstantAlias(_)) {
|
|
868
886
|
return match self.resolve_to_namespace(attached_id) {
|
|
@@ -874,7 +892,7 @@ impl<'a> Resolver<'a> {
|
|
|
874
892
|
if matches!(attached_decl, Declaration::Constant(_)) {
|
|
875
893
|
if self.graph.all_definitions_promotable(attached_decl) {
|
|
876
894
|
self.graph.promote_constant_to_namespace(attached_id, |name, owner_id| {
|
|
877
|
-
Declaration::Namespace(Namespace::
|
|
895
|
+
Declaration::Namespace(Namespace::Todo(Box::new(TodoDeclaration::new(name, owner_id))))
|
|
878
896
|
});
|
|
879
897
|
|
|
880
898
|
self.schedule_singleton_ancestors(attached_id, mode);
|
|
@@ -890,10 +908,6 @@ impl<'a> Resolver<'a> {
|
|
|
890
908
|
.as_namespace_mut()
|
|
891
909
|
.expect("constants are handled above; all other callers pass namespace declarations");
|
|
892
910
|
|
|
893
|
-
if let Some(singleton_id) = namespace_decl.singleton_class() {
|
|
894
|
-
return Some(*singleton_id);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
911
|
let decl_id = DeclarationId::from(&fully_qualified_name);
|
|
898
912
|
namespace_decl.set_singleton_class_id(decl_id);
|
|
899
913
|
|
|
@@ -1067,17 +1081,11 @@ impl<'a> Resolver<'a> {
|
|
|
1067
1081
|
declaration_id: DeclarationId,
|
|
1068
1082
|
context: &mut LinearizationContext,
|
|
1069
1083
|
) -> Option<Vec<Ancestor>> {
|
|
1070
|
-
if declaration_id == *BASIC_OBJECT_ID {
|
|
1071
|
-
return None;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
1084
|
let declaration = self.graph.declarations().get(&declaration_id).unwrap();
|
|
1075
1085
|
|
|
1076
1086
|
match declaration {
|
|
1077
1087
|
Declaration::Namespace(Namespace::Class(_)) => {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
Some(match self.linearize_parent_class(&definition_ids, context) {
|
|
1088
|
+
Some(match self.linearize_superclass(declaration_id, context)? {
|
|
1081
1089
|
Ancestors::Complete(ids) => ids,
|
|
1082
1090
|
Ancestors::Cyclic(ids) => {
|
|
1083
1091
|
context.cyclic = true;
|
|
@@ -1229,14 +1237,16 @@ impl<'a> Resolver<'a> {
|
|
|
1229
1237
|
if !descendants.is_empty() {
|
|
1230
1238
|
for ancestor in cached {
|
|
1231
1239
|
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
1240
|
+
let namespace = self
|
|
1241
|
+
.graph
|
|
1242
|
+
.declarations_mut()
|
|
1243
|
+
.get_mut(ancestor_id)
|
|
1244
|
+
.unwrap()
|
|
1245
|
+
.as_namespace_mut()
|
|
1246
|
+
.unwrap();
|
|
1247
|
+
|
|
1232
1248
|
for descendant in descendants.iter() {
|
|
1233
|
-
|
|
1234
|
-
.declarations_mut()
|
|
1235
|
-
.get_mut(ancestor_id)
|
|
1236
|
-
.unwrap()
|
|
1237
|
-
.as_namespace_mut()
|
|
1238
|
-
.unwrap()
|
|
1239
|
-
.add_descendant(*descendant);
|
|
1249
|
+
namespace.add_descendant(*descendant);
|
|
1240
1250
|
}
|
|
1241
1251
|
}
|
|
1242
1252
|
}
|
|
@@ -1280,8 +1290,8 @@ impl<'a> Resolver<'a> {
|
|
|
1280
1290
|
Outcome::Resolved(owner_id) => {
|
|
1281
1291
|
let mut fully_qualified_name = self.graph.strings().get(&str_id).unwrap().to_string();
|
|
1282
1292
|
|
|
1283
|
-
// If the owner is a promotable constant and something is being defined inside it, promote it to
|
|
1284
|
-
// module
|
|
1293
|
+
// If the owner is a promotable constant and something is being defined inside it, promote it to an
|
|
1294
|
+
// unknown namespace. A later explicit class or module definition selects the concrete kind.
|
|
1285
1295
|
{
|
|
1286
1296
|
let owner = self.graph.declarations().get(&owner_id).unwrap();
|
|
1287
1297
|
let is_promotable_constant =
|
|
@@ -1289,7 +1299,7 @@ impl<'a> Resolver<'a> {
|
|
|
1289
1299
|
|
|
1290
1300
|
if is_promotable_constant {
|
|
1291
1301
|
self.graph.promote_constant_to_namespace(owner_id, |name, owner_id| {
|
|
1292
|
-
Declaration::Namespace(Namespace::
|
|
1302
|
+
Declaration::Namespace(Namespace::Todo(Box::new(TodoDeclaration::new(name, owner_id))))
|
|
1293
1303
|
});
|
|
1294
1304
|
self.unit_queue.push_back(Unit::Ancestors(owner_id));
|
|
1295
1305
|
}
|
|
@@ -1468,139 +1478,139 @@ impl<'a> Resolver<'a> {
|
|
|
1468
1478
|
/// resolved
|
|
1469
1479
|
#[allow(clippy::too_many_lines)]
|
|
1470
1480
|
fn resolve_constant_internal(&mut self, name_id: NameId) -> Outcome {
|
|
1471
|
-
let
|
|
1481
|
+
let name = match self.graph.names().get(&name_id).unwrap() {
|
|
1482
|
+
NameRef::Resolved(resolved) => return Outcome::Resolved(*resolved.declaration_id()),
|
|
1483
|
+
NameRef::Unresolved(name) => name.as_ref().clone(),
|
|
1484
|
+
};
|
|
1472
1485
|
|
|
1473
|
-
match
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
ParentScope::TopLevel => {
|
|
1477
|
-
let result = self.search_ancestors(*OBJECT_ID, *name.str());
|
|
1486
|
+
match name.parent_scope() {
|
|
1487
|
+
ParentScope::TopLevel => {
|
|
1488
|
+
let result = self.search_ancestors(*OBJECT_ID, *name.str());
|
|
1478
1489
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1490
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1491
|
+
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1492
|
+
}
|
|
1482
1493
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1494
|
+
result
|
|
1495
|
+
}
|
|
1496
|
+
ParentScope::Attached(parent_scope_id) => {
|
|
1497
|
+
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1498
|
+
return Outcome::Retry {
|
|
1499
|
+
partial_ancestors: false,
|
|
1500
|
+
};
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
let mut target_decl_id = *parent_scope.declaration_id();
|
|
1504
|
+
let target_decl = self.graph.declarations().get(&target_decl_id).unwrap();
|
|
1505
|
+
|
|
1506
|
+
// If the attached object is a constant alias, resolve it to the target namespace
|
|
1507
|
+
// (e.g., ALIAS.bar where ALIAS = Foo should create the singleton class on Foo, not ALIAS)
|
|
1508
|
+
if matches!(target_decl, Declaration::ConstantAlias(_)) {
|
|
1509
|
+
let resolved_ids = self.resolve_alias_chains(target_decl_id);
|
|
1510
|
+
|
|
1511
|
+
if resolved_ids
|
|
1512
|
+
.iter()
|
|
1513
|
+
.any(|id| matches!(self.graph.declarations().get(id), Some(Declaration::ConstantAlias(_))))
|
|
1514
|
+
{
|
|
1515
|
+
return Outcome::Retry {
|
|
1516
|
+
partial_ancestors: false,
|
|
1490
1517
|
};
|
|
1518
|
+
}
|
|
1491
1519
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1520
|
+
let Some(&namespace_id) = resolved_ids
|
|
1521
|
+
.iter()
|
|
1522
|
+
.find(|id| matches!(self.graph.declarations().get(id), Some(Declaration::Namespace(_))))
|
|
1523
|
+
else {
|
|
1524
|
+
return Outcome::Unresolved;
|
|
1525
|
+
};
|
|
1494
1526
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if matches!(target_decl, Declaration::ConstantAlias(_)) {
|
|
1498
|
-
let resolved_ids = self.resolve_alias_chains(target_decl_id);
|
|
1527
|
+
target_decl_id = namespace_id;
|
|
1528
|
+
}
|
|
1499
1529
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1530
|
+
// If we found a singleton reference with a resolved attached object parent scope, we
|
|
1531
|
+
// automatically create the singleton class
|
|
1532
|
+
let Some(singleton_id) =
|
|
1533
|
+
self.get_or_create_singleton_class(target_decl_id, SingletonAncestors::Enqueue)
|
|
1534
|
+
else {
|
|
1535
|
+
return Outcome::Unresolved;
|
|
1536
|
+
};
|
|
1537
|
+
self.graph.record_resolved_name(name_id, singleton_id);
|
|
1538
|
+
// `get_or_create_singleton_class` already enqueued the singleton's ancestors on
|
|
1539
|
+
// creation, so there is nothing to hand back for re-enqueueing.
|
|
1540
|
+
Outcome::Resolved(singleton_id)
|
|
1541
|
+
}
|
|
1542
|
+
ParentScope::None => {
|
|
1543
|
+
// Otherwise, it's a simple constant read and we can resolve it directly
|
|
1544
|
+
let result = self.run_resolution(&name);
|
|
1507
1545
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
return Outcome::Unresolved;
|
|
1512
|
-
};
|
|
1546
|
+
if let Outcome::Resolved(declaration_id) = result {
|
|
1547
|
+
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1548
|
+
}
|
|
1513
1549
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1550
|
+
result
|
|
1551
|
+
}
|
|
1552
|
+
ParentScope::Some(parent_scope_id) => {
|
|
1553
|
+
let NameRef::Resolved(parent_scope) = self.graph.names().get(parent_scope_id).unwrap() else {
|
|
1554
|
+
return Outcome::Retry {
|
|
1555
|
+
partial_ancestors: false,
|
|
1556
|
+
};
|
|
1557
|
+
};
|
|
1516
1558
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
self.get_or_create_singleton_class(target_decl_id, SingletonAncestors::Enqueue)
|
|
1521
|
-
else {
|
|
1522
|
-
return Outcome::Unresolved;
|
|
1523
|
-
};
|
|
1524
|
-
self.graph.record_resolved_name(name_id, singleton_id);
|
|
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)
|
|
1528
|
-
}
|
|
1529
|
-
ParentScope::None => {
|
|
1530
|
-
// Otherwise, it's a simple constant read and we can resolve it directly
|
|
1531
|
-
let result = self.run_resolution(&name);
|
|
1559
|
+
// Resolve the namespace in case it's an alias (e.g., ALIAS::CONST where ALIAS = Foo)
|
|
1560
|
+
// An alias can have multiple targets, so we try all of them in order.
|
|
1561
|
+
let resolved_ids = self.resolve_alias_chains(*parent_scope.declaration_id());
|
|
1532
1562
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1563
|
+
// Search each resolved target for the constant. Return early if found.
|
|
1564
|
+
let mut missing_partial = false;
|
|
1565
|
+
let mut found_namespace = false;
|
|
1536
1566
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1567
|
+
for &id in &resolved_ids {
|
|
1568
|
+
match self.graph.declarations().get(&id) {
|
|
1569
|
+
Some(Declaration::ConstantAlias(_)) => {
|
|
1570
|
+
// Alias not fully resolved yet
|
|
1541
1571
|
return Outcome::Retry {
|
|
1542
1572
|
partial_ancestors: false,
|
|
1543
1573
|
};
|
|
1544
|
-
}
|
|
1574
|
+
}
|
|
1575
|
+
Some(Declaration::Namespace(_)) => {
|
|
1576
|
+
found_namespace = true;
|
|
1545
1577
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
// Search each resolved target for the constant. Return early if found.
|
|
1551
|
-
let mut missing_partial = false;
|
|
1552
|
-
let mut found_namespace = false;
|
|
1553
|
-
|
|
1554
|
-
for &id in &resolved_ids {
|
|
1555
|
-
match self.graph.declarations().get(&id) {
|
|
1556
|
-
Some(Declaration::ConstantAlias(_)) => {
|
|
1557
|
-
// Alias not fully resolved yet
|
|
1558
|
-
return Outcome::Retry {
|
|
1559
|
-
partial_ancestors: false,
|
|
1560
|
-
};
|
|
1578
|
+
match self.search_ancestors(id, *name.str()) {
|
|
1579
|
+
Outcome::Resolved(declaration_id) => {
|
|
1580
|
+
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1581
|
+
return Outcome::Resolved(declaration_id);
|
|
1561
1582
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
Outcome::Resolved(declaration_id) => {
|
|
1567
|
-
self.graph.record_resolved_name(name_id, declaration_id);
|
|
1568
|
-
return Outcome::Resolved(declaration_id);
|
|
1569
|
-
}
|
|
1570
|
-
Outcome::Retry {
|
|
1571
|
-
partial_ancestors: true,
|
|
1572
|
-
} => {
|
|
1573
|
-
missing_partial = true;
|
|
1574
|
-
}
|
|
1575
|
-
Outcome::Unresolved => {}
|
|
1576
|
-
Outcome::Retry {
|
|
1577
|
-
partial_ancestors: false,
|
|
1578
|
-
} => {
|
|
1579
|
-
unreachable!("search_ancestors never returns a non-partial Retry")
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1583
|
+
Outcome::Retry {
|
|
1584
|
+
partial_ancestors: true,
|
|
1585
|
+
} => {
|
|
1586
|
+
missing_partial = true;
|
|
1582
1587
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1588
|
+
Outcome::Unresolved => {}
|
|
1589
|
+
Outcome::Retry {
|
|
1590
|
+
partial_ancestors: false,
|
|
1591
|
+
} => {
|
|
1592
|
+
unreachable!("search_ancestors never returns a non-partial Retry")
|
|
1585
1593
|
}
|
|
1586
1594
|
}
|
|
1587
1595
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if !found_namespace {
|
|
1591
|
-
return Outcome::Unresolved;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
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,
|
|
1596
|
+
_ => {
|
|
1597
|
+
// Not a namespace (e.g., a constant) - skip
|
|
1599
1598
|
}
|
|
1600
1599
|
}
|
|
1601
1600
|
}
|
|
1601
|
+
|
|
1602
|
+
// If no namespaces were found, this constant path can never resolve.
|
|
1603
|
+
if !found_namespace {
|
|
1604
|
+
return Outcome::Unresolved;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// Member not found in any namespace yet - retry in case it's added later. `partial` records
|
|
1608
|
+
// whether the miss was due to a still-partial ancestor chain, so the unit is re-checked once
|
|
1609
|
+
// that chain completes.
|
|
1610
|
+
Outcome::Retry {
|
|
1611
|
+
partial_ancestors: missing_partial,
|
|
1612
|
+
}
|
|
1602
1613
|
}
|
|
1603
|
-
NameRef::Resolved(resolved) => Outcome::Resolved(*resolved.declaration_id()),
|
|
1604
1614
|
}
|
|
1605
1615
|
}
|
|
1606
1616
|
|
|
@@ -1884,19 +1894,6 @@ impl<'a> Resolver<'a> {
|
|
|
1884
1894
|
depth
|
|
1885
1895
|
}
|
|
1886
1896
|
|
|
1887
|
-
/// Pre-compute name depths for all names into a `NameId → depth` map. Each name's depth is
|
|
1888
|
-
/// computed once via memoized recursion, then used as an O(1) lookup key during sorting in
|
|
1889
|
-
/// `prepare_units`.
|
|
1890
|
-
pub(crate) fn compute_name_depths(names: &IdentityHashMap<NameId, NameRef>) -> IdentityHashMap<NameId, u32> {
|
|
1891
|
-
let mut cache = IdentityHashMap::with_capacity_and_hasher(names.len(), IdentityHashBuilder);
|
|
1892
|
-
|
|
1893
|
-
for &name_id in names.keys() {
|
|
1894
|
-
Self::name_depth(name_id, names, &mut cache);
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
cache
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
1897
|
/// Drains `pending_work` and classifies items into the resolution queue.
|
|
1901
1898
|
/// Namespace definitions and constant references are sorted by name depth for deterministic
|
|
1902
1899
|
/// resolution order. Non-namespace definitions (methods, attrs, variables) are returned
|
|
@@ -1910,7 +1907,28 @@ impl<'a> Resolver<'a> {
|
|
|
1910
1907
|
let mut const_refs = Vec::new();
|
|
1911
1908
|
let mut ancestors = vec![*BASIC_OBJECT_ID, *KERNEL_ID, *OBJECT_ID, *MODULE_ID, *CLASS_ID];
|
|
1912
1909
|
let names = self.graph.names();
|
|
1913
|
-
|
|
1910
|
+
// Memoize name depths on demand for only the names referenced by the pending work. During incremental
|
|
1911
|
+
// resolution this avoids walking every name in the graph just to sort the small subset of units below.
|
|
1912
|
+
// A pending unit references at most one name, so `estimated` (bounded by the total number of names) is a
|
|
1913
|
+
// reasonable capacity that avoids rehashing as depths accumulate.
|
|
1914
|
+
let mut depths: IdentityHashMap<NameId, u32> =
|
|
1915
|
+
IdentityHashMap::with_capacity_and_hasher(estimated.min(names.len()), IdentityHashBuilder);
|
|
1916
|
+
|
|
1917
|
+
// Precompute the lexicographic rank of every document URI. Definitions and references are sorted by
|
|
1918
|
+
// (name depth, URI, offset) below; storing precomputed integer sort keys instead of looking up name
|
|
1919
|
+
// depths and comparing URI strings on every comparison makes sorting substantially cheaper on large graphs.
|
|
1920
|
+
let mut uris: Vec<(&str, UriId)> = self
|
|
1921
|
+
.graph
|
|
1922
|
+
.documents()
|
|
1923
|
+
.iter()
|
|
1924
|
+
.map(|(uri_id, document)| (document.uri(), *uri_id))
|
|
1925
|
+
.collect();
|
|
1926
|
+
uris.sort_unstable();
|
|
1927
|
+
let mut uri_ranks: IdentityHashMap<UriId, u32> =
|
|
1928
|
+
IdentityHashMap::with_capacity_and_hasher(uris.len(), IdentityHashBuilder);
|
|
1929
|
+
for (rank, (_, uri_id)) in uris.into_iter().enumerate() {
|
|
1930
|
+
uri_ranks.insert(uri_id, u32::try_from(rank).expect("more documents than u32::MAX"));
|
|
1931
|
+
}
|
|
1914
1932
|
|
|
1915
1933
|
// Dedup: when multiple files are indexed before resolution runs, pending_work accumulates
|
|
1916
1934
|
// and the same definition/reference ID can be enqueued more than once.
|
|
@@ -1928,23 +1946,28 @@ impl<'a> Resolver<'a> {
|
|
|
1928
1946
|
let Some(definition) = self.graph.definitions().get(&id) else {
|
|
1929
1947
|
continue;
|
|
1930
1948
|
};
|
|
1931
|
-
let
|
|
1949
|
+
let uri_rank = *uri_ranks.get(definition.uri_id()).unwrap();
|
|
1932
1950
|
|
|
1933
1951
|
match definition {
|
|
1934
1952
|
Definition::Class(def) => {
|
|
1935
|
-
|
|
1953
|
+
let depth = Self::name_depth(*def.name_id(), names, &mut depths);
|
|
1954
|
+
definitions.push((Unit::Definition(id), (depth, uri_rank, definition.offset())));
|
|
1936
1955
|
}
|
|
1937
1956
|
Definition::Module(def) => {
|
|
1938
|
-
|
|
1957
|
+
let depth = Self::name_depth(*def.name_id(), names, &mut depths);
|
|
1958
|
+
definitions.push((Unit::Definition(id), (depth, uri_rank, definition.offset())));
|
|
1939
1959
|
}
|
|
1940
1960
|
Definition::Constant(def) => {
|
|
1941
|
-
|
|
1961
|
+
let depth = Self::name_depth(*def.name_id(), names, &mut depths);
|
|
1962
|
+
definitions.push((Unit::Definition(id), (depth, uri_rank, definition.offset())));
|
|
1942
1963
|
}
|
|
1943
1964
|
Definition::ConstantAlias(def) => {
|
|
1944
|
-
|
|
1965
|
+
let depth = Self::name_depth(*def.name_id(), names, &mut depths);
|
|
1966
|
+
definitions.push((Unit::Definition(id), (depth, uri_rank, definition.offset())));
|
|
1945
1967
|
}
|
|
1946
1968
|
Definition::SingletonClass(def) => {
|
|
1947
|
-
|
|
1969
|
+
let depth = Self::name_depth(*def.name_id(), names, &mut depths);
|
|
1970
|
+
definitions.push((Unit::Definition(id), (depth, uri_rank, definition.offset())));
|
|
1948
1971
|
}
|
|
1949
1972
|
// SelfReceiver methods create singleton classes, which need
|
|
1950
1973
|
// ancestor linearization. Process them in the convergence loop
|
|
@@ -1965,11 +1988,9 @@ impl<'a> Resolver<'a> {
|
|
|
1965
1988
|
let Some(constant_ref) = self.graph.constant_references().get(&id) else {
|
|
1966
1989
|
continue;
|
|
1967
1990
|
};
|
|
1968
|
-
let
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
(*constant_ref.name_id(), uri, constant_ref.offset()),
|
|
1972
|
-
));
|
|
1991
|
+
let uri_rank = *uri_ranks.get(&constant_ref.uri_id()).unwrap();
|
|
1992
|
+
let depth = Self::name_depth(*constant_ref.name_id(), names, &mut depths);
|
|
1993
|
+
const_refs.push((Unit::ConstantRef(id), (depth, uri_rank, constant_ref.offset())));
|
|
1973
1994
|
}
|
|
1974
1995
|
Unit::Ancestors(id) => {
|
|
1975
1996
|
if !seen_ancestors.insert(id) {
|
|
@@ -1984,14 +2005,10 @@ impl<'a> Resolver<'a> {
|
|
|
1984
2005
|
}
|
|
1985
2006
|
|
|
1986
2007
|
// Sort namespaces based on their name complexity so that simpler names are always first
|
|
1987
|
-
// When the depth is the same, sort by URI and offset to maintain determinism
|
|
1988
|
-
definitions.
|
|
1989
|
-
(depths.get(name_a).unwrap(), uri_a, offset_a).cmp(&(depths.get(name_b).unwrap(), uri_b, offset_b))
|
|
1990
|
-
});
|
|
2008
|
+
// When the depth is the same, sort by URI rank and offset to maintain determinism
|
|
2009
|
+
definitions.sort_unstable_by_key(|(_, key)| *key);
|
|
1991
2010
|
|
|
1992
|
-
const_refs.
|
|
1993
|
-
(depths.get(name_a).unwrap(), uri_a, offset_a).cmp(&(depths.get(name_b).unwrap(), uri_b, offset_b))
|
|
1994
|
-
});
|
|
2011
|
+
const_refs.sort_unstable_by_key(|(_, key)| *key);
|
|
1995
2012
|
|
|
1996
2013
|
others.sort_unstable_by_key(|(_, key)| *key);
|
|
1997
2014
|
|
|
@@ -2011,15 +2028,11 @@ impl<'a> Resolver<'a> {
|
|
|
2011
2028
|
/// - Class: parent is the singleton class of the original parent class
|
|
2012
2029
|
/// - Singleton class: recurse as many times as necessary to wrap the original attached object's parent class
|
|
2013
2030
|
fn singleton_parent_id(&mut self, attached_id: DeclarationId) -> (DeclarationId, bool) {
|
|
2014
|
-
// Base case: if we reached `BasicObject`, then the parent is `Class`
|
|
2015
|
-
if attached_id == *BASIC_OBJECT_ID {
|
|
2016
|
-
return (*CLASS_ID, false);
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
2031
|
let decl = self.graph.declarations().get(&attached_id).unwrap();
|
|
2020
2032
|
|
|
2021
2033
|
match decl {
|
|
2022
|
-
|
|
2034
|
+
// Todo may later become a class or module. Until then, use Module as the singleton-parent fallback.
|
|
2035
|
+
Declaration::Namespace(Namespace::Module(_) | Namespace::Todo(_)) => (*MODULE_ID, false),
|
|
2023
2036
|
Declaration::Namespace(Namespace::SingletonClass(_)) => {
|
|
2024
2037
|
// For singleton classes, we keep recursively wrapping parents until we can reach the original attached
|
|
2025
2038
|
// object
|
|
@@ -2033,14 +2046,15 @@ impl<'a> Resolver<'a> {
|
|
|
2033
2046
|
)
|
|
2034
2047
|
}
|
|
2035
2048
|
Declaration::Namespace(Namespace::Class(_)) => {
|
|
2036
|
-
// For classes (the regular case), we need to return the singleton class of its
|
|
2037
|
-
let
|
|
2038
|
-
|
|
2039
|
-
|
|
2049
|
+
// For classes (the regular case), we need to return the singleton class of its superclass
|
|
2050
|
+
let Some((superclass_id, unresolved_superclass)) = self.get_superclass(attached_id) else {
|
|
2051
|
+
// BasicObject has no superclass, but its singleton class inherits from Class
|
|
2052
|
+
return (*CLASS_ID, false);
|
|
2053
|
+
};
|
|
2040
2054
|
(
|
|
2041
|
-
self.get_or_create_singleton_class(
|
|
2042
|
-
.expect("
|
|
2043
|
-
|
|
2055
|
+
self.get_or_create_singleton_class(superclass_id, SingletonAncestors::Deferred)
|
|
2056
|
+
.expect("superclass should always be a namespace"),
|
|
2057
|
+
unresolved_superclass.is_some(),
|
|
2044
2058
|
)
|
|
2045
2059
|
}
|
|
2046
2060
|
_ => {
|
|
@@ -2051,11 +2065,18 @@ impl<'a> Resolver<'a> {
|
|
|
2051
2065
|
}
|
|
2052
2066
|
}
|
|
2053
2067
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2068
|
+
/// Returns the selected superclass and any unresolved explicit superclass. The top-level `BasicObject` declaration
|
|
2069
|
+
/// is Ruby's root class and is the only class without a superclass.
|
|
2070
|
+
fn get_superclass(&self, declaration_id: DeclarationId) -> Option<(DeclarationId, Option<NameId>)> {
|
|
2071
|
+
if declaration_id == *BASIC_OBJECT_ID {
|
|
2072
|
+
return None;
|
|
2073
|
+
}
|
|
2057
2074
|
|
|
2058
|
-
|
|
2075
|
+
let declaration = self.graph.declarations().get(&declaration_id).unwrap();
|
|
2076
|
+
let mut explicit_superclasses = Vec::new();
|
|
2077
|
+
let mut unresolved_superclass = None;
|
|
2078
|
+
|
|
2079
|
+
for definition_id in declaration.definitions() {
|
|
2059
2080
|
let definition = self.graph.definitions().get(definition_id).unwrap();
|
|
2060
2081
|
|
|
2061
2082
|
if let Definition::Class(class) = definition
|
|
@@ -2066,47 +2087,47 @@ impl<'a> Resolver<'a> {
|
|
|
2066
2087
|
|
|
2067
2088
|
match name {
|
|
2068
2089
|
NameRef::Resolved(resolved) => {
|
|
2069
|
-
if let Some(
|
|
2070
|
-
|
|
2090
|
+
if let Some(superclass_id) = self.resolve_to_namespace(*resolved.declaration_id()) {
|
|
2091
|
+
explicit_superclasses.push(superclass_id);
|
|
2071
2092
|
}
|
|
2072
2093
|
}
|
|
2073
2094
|
NameRef::Unresolved(_) => {
|
|
2074
|
-
|
|
2095
|
+
unresolved_superclass = Some(*constant_reference.name_id());
|
|
2075
2096
|
}
|
|
2076
2097
|
}
|
|
2077
2098
|
}
|
|
2078
2099
|
}
|
|
2079
2100
|
|
|
2080
|
-
// If there's more than one
|
|
2101
|
+
// If there's more than one superclass that isn't `Object` and they are different, then there's a superclass
|
|
2081
2102
|
// mismatch error. TODO: We should add a diagnostic here
|
|
2082
|
-
(
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
)
|
|
2103
|
+
Some((
|
|
2104
|
+
explicit_superclasses.first().copied().unwrap_or(*OBJECT_ID),
|
|
2105
|
+
unresolved_superclass,
|
|
2106
|
+
))
|
|
2086
2107
|
}
|
|
2087
2108
|
|
|
2088
|
-
fn
|
|
2109
|
+
fn linearize_superclass(
|
|
2089
2110
|
&mut self,
|
|
2090
|
-
|
|
2111
|
+
declaration_id: DeclarationId,
|
|
2091
2112
|
context: &mut LinearizationContext,
|
|
2092
|
-
) -> Ancestors {
|
|
2093
|
-
let (
|
|
2094
|
-
let mut result = self.linearize_ancestors(
|
|
2113
|
+
) -> Option<Ancestors> {
|
|
2114
|
+
let (superclass_id, unresolved_superclass) = self.get_superclass(declaration_id)?;
|
|
2115
|
+
let mut result = self.linearize_ancestors(superclass_id, context);
|
|
2095
2116
|
|
|
2096
|
-
if let Some(name_id) =
|
|
2117
|
+
if let Some(name_id) = unresolved_superclass {
|
|
2097
2118
|
context.partial = true;
|
|
2098
2119
|
|
|
2099
|
-
// Insert the unresolved
|
|
2120
|
+
// Insert the unresolved superclass as a Partial ancestor at the front of the chain, so it
|
|
2100
2121
|
// appears before the default Object ancestors
|
|
2101
2122
|
let ancestors = match &mut result {
|
|
2102
2123
|
Ancestors::Complete(ids) | Ancestors::Cyclic(ids) | Ancestors::Partial(ids) => ids,
|
|
2103
2124
|
};
|
|
2104
2125
|
ancestors.insert(0, Ancestor::Partial(name_id));
|
|
2105
2126
|
|
|
2106
|
-
result.to_partial()
|
|
2107
|
-
} else {
|
|
2108
|
-
result
|
|
2127
|
+
result = result.to_partial();
|
|
2109
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
Some(result)
|
|
2110
2131
|
}
|
|
2111
2132
|
|
|
2112
2133
|
fn mixins_of(&self, definition_id: DefinitionId) -> Option<Vec<Mixin>> {
|