rubydex 0.2.0-aarch64-linux → 0.2.1-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/rubydex/declaration.c +38 -0
- data/ext/rubydex/graph.c +70 -17
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/declaration.rb +31 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +41 -11
- data/rust/rubydex/src/diagnostic.rs +1 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +2 -5
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +14 -7
- data/rust/rubydex/src/model/declaration.rs +48 -0
- data/rust/rubydex/src/model/definitions.rs +18 -9
- data/rust/rubydex/src/model/graph.rs +237 -35
- data/rust/rubydex/src/query.rs +1475 -159
- data/rust/rubydex/src/resolution.rs +173 -61
- data/rust/rubydex/src/resolution_tests.rs +178 -0
- data/rust/rubydex-sys/src/declaration_api.rs +19 -0
- data/rust/rubydex-sys/src/graph_api.rs +89 -5
- metadata +2 -2
|
@@ -287,11 +287,8 @@ impl<'a> Resolver<'a> {
|
|
|
287
287
|
unreachable!("SelfReceiver methods should be routed to handle_definition_unit");
|
|
288
288
|
}
|
|
289
289
|
Some(Receiver::ConstantReceiver(name_id)) => {
|
|
290
|
-
let receiver_decl_id =
|
|
291
|
-
|
|
292
|
-
NameRef::Unresolved(_) => {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
290
|
+
let Some(receiver_decl_id) = self.resolve_constant_receiver(*name_id, id) else {
|
|
291
|
+
continue;
|
|
295
292
|
};
|
|
296
293
|
|
|
297
294
|
let Some(singleton_id) = self.get_or_create_singleton_class(receiver_decl_id, true) else {
|
|
@@ -301,8 +298,8 @@ impl<'a> Resolver<'a> {
|
|
|
301
298
|
singleton_id
|
|
302
299
|
}
|
|
303
300
|
None => {
|
|
304
|
-
let
|
|
305
|
-
else {
|
|
301
|
+
let lexical = *method_definition.lexical_nesting_id();
|
|
302
|
+
let Some(resolved) = self.resolve_lexical_owner(lexical, id) else {
|
|
306
303
|
continue;
|
|
307
304
|
};
|
|
308
305
|
resolved
|
|
@@ -314,29 +311,35 @@ impl<'a> Resolver<'a> {
|
|
|
314
311
|
});
|
|
315
312
|
}
|
|
316
313
|
Definition::AttrAccessor(attr) => {
|
|
317
|
-
let
|
|
314
|
+
let lexical = *attr.lexical_nesting_id();
|
|
315
|
+
let str_id = *attr.str_id();
|
|
316
|
+
let Some(owner_id) = self.resolve_lexical_owner(lexical, id) else {
|
|
318
317
|
continue;
|
|
319
318
|
};
|
|
320
319
|
|
|
321
|
-
self.create_declaration(
|
|
320
|
+
self.create_declaration(str_id, id, owner_id, |name| {
|
|
322
321
|
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
|
|
323
322
|
});
|
|
324
323
|
}
|
|
325
324
|
Definition::AttrReader(attr) => {
|
|
326
|
-
let
|
|
325
|
+
let lexical = *attr.lexical_nesting_id();
|
|
326
|
+
let str_id = *attr.str_id();
|
|
327
|
+
let Some(owner_id) = self.resolve_lexical_owner(lexical, id) else {
|
|
327
328
|
continue;
|
|
328
329
|
};
|
|
329
330
|
|
|
330
|
-
self.create_declaration(
|
|
331
|
+
self.create_declaration(str_id, id, owner_id, |name| {
|
|
331
332
|
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
|
|
332
333
|
});
|
|
333
334
|
}
|
|
334
335
|
Definition::AttrWriter(attr) => {
|
|
335
|
-
let
|
|
336
|
+
let lexical = *attr.lexical_nesting_id();
|
|
337
|
+
let str_id = *attr.str_id();
|
|
338
|
+
let Some(owner_id) = self.resolve_lexical_owner(lexical, id) else {
|
|
336
339
|
continue;
|
|
337
340
|
};
|
|
338
341
|
|
|
339
|
-
self.create_declaration(
|
|
342
|
+
self.create_declaration(str_id, id, owner_id, |name| {
|
|
340
343
|
Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
|
|
341
344
|
});
|
|
342
345
|
}
|
|
@@ -377,11 +380,11 @@ impl<'a> Resolver<'a> {
|
|
|
377
380
|
.definition_id_to_declaration_id(*def_id)
|
|
378
381
|
.expect("SelfReceiver definition should have a declaration"),
|
|
379
382
|
Receiver::ConstantReceiver(name_id) => {
|
|
380
|
-
let Some(
|
|
383
|
+
let Some(receiver_decl_id) = self.resolve_constant_receiver(*name_id, id)
|
|
384
|
+
else {
|
|
381
385
|
continue;
|
|
382
386
|
};
|
|
383
|
-
|
|
384
|
-
*resolved.declaration_id()
|
|
387
|
+
receiver_decl_id
|
|
385
388
|
}
|
|
386
389
|
};
|
|
387
390
|
|
|
@@ -407,7 +410,8 @@ impl<'a> Resolver<'a> {
|
|
|
407
410
|
}
|
|
408
411
|
|
|
409
412
|
// If the method has no explicit receiver, we resolve the owner based on the lexical nesting
|
|
410
|
-
let Some(method_owner_id) = self.resolve_lexical_owner(*method.lexical_nesting_id())
|
|
413
|
+
let Some(method_owner_id) = self.resolve_lexical_owner(*method.lexical_nesting_id(), id)
|
|
414
|
+
else {
|
|
411
415
|
continue;
|
|
412
416
|
};
|
|
413
417
|
|
|
@@ -464,11 +468,14 @@ impl<'a> Resolver<'a> {
|
|
|
464
468
|
// If in a singleton class body directly, the owner is the singleton class's singleton class
|
|
465
469
|
// Like `class << Foo; @bar = 1; end`, where `@bar` is owned by `Foo::<Foo>::<<Foo>>`
|
|
466
470
|
Definition::SingletonClass(_) => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
471
|
+
// The singleton's declaration may be missing (e.g. its receiver was
|
|
472
|
+
// just deleted). Re-queue and let the next resolve place `@bar` on
|
|
473
|
+
// the right owner instead of falling back to Object.
|
|
474
|
+
let Some(&singleton_class_decl_id) = self.graph.definition_id_to_declaration_id(nesting_id)
|
|
475
|
+
else {
|
|
476
|
+
self.graph.push_work(Unit::Definition(id));
|
|
477
|
+
continue;
|
|
478
|
+
};
|
|
472
479
|
let owner_id = self
|
|
473
480
|
.get_or_create_singleton_class(singleton_class_decl_id, true)
|
|
474
481
|
.expect("singleton class nesting should always be a namespace");
|
|
@@ -515,14 +522,15 @@ impl<'a> Resolver<'a> {
|
|
|
515
522
|
};
|
|
516
523
|
owner_id
|
|
517
524
|
}
|
|
518
|
-
Some(Receiver::ConstantReceiver(name_id)) =>
|
|
519
|
-
|
|
520
|
-
NameRef::Unresolved(_) => {
|
|
525
|
+
Some(Receiver::ConstantReceiver(name_id)) => {
|
|
526
|
+
let Some(resolved) = self.resolve_constant_receiver(*name_id, id) else {
|
|
521
527
|
continue;
|
|
522
|
-
}
|
|
523
|
-
|
|
528
|
+
};
|
|
529
|
+
resolved
|
|
530
|
+
}
|
|
524
531
|
None => {
|
|
525
|
-
let
|
|
532
|
+
let lexical = *alias.lexical_nesting_id();
|
|
533
|
+
let Some(resolved) = self.resolve_lexical_owner(lexical, id) else {
|
|
526
534
|
continue;
|
|
527
535
|
};
|
|
528
536
|
resolved
|
|
@@ -538,8 +546,63 @@ impl<'a> Resolver<'a> {
|
|
|
538
546
|
Declaration::GlobalVariable(Box::new(GlobalVariableDeclaration::new(name, *OBJECT_ID)))
|
|
539
547
|
});
|
|
540
548
|
}
|
|
541
|
-
Definition::ConstantVisibility(
|
|
542
|
-
//
|
|
549
|
+
Definition::ConstantVisibility(constant_visibility) => {
|
|
550
|
+
// Both `private_constant` and `public_constant` can only target direct members.
|
|
551
|
+
// Inheritance or surrounding lexical scopes are not taken into account.
|
|
552
|
+
let receiver = *constant_visibility.receiver();
|
|
553
|
+
let target = *constant_visibility.target();
|
|
554
|
+
let uri_id = *constant_visibility.uri_id();
|
|
555
|
+
let offset = constant_visibility.offset().clone();
|
|
556
|
+
let lexical_nesting_id = *constant_visibility.lexical_nesting_id();
|
|
557
|
+
let constant_name = self.graph.strings().get(&target).unwrap().as_str().to_string();
|
|
558
|
+
|
|
559
|
+
let owner_id = if let Some(receiver_name_id) = receiver {
|
|
560
|
+
let NameRef::Resolved(resolved_receiver) = self.graph.names().get(&receiver_name_id).unwrap()
|
|
561
|
+
else {
|
|
562
|
+
continue;
|
|
563
|
+
};
|
|
564
|
+
let Some(namespace_id) = self.resolve_to_namespace(*resolved_receiver.declaration_id()) else {
|
|
565
|
+
continue;
|
|
566
|
+
};
|
|
567
|
+
namespace_id
|
|
568
|
+
} else {
|
|
569
|
+
let Some(decl_id) = self.resolve_lexical_owner(lexical_nesting_id, id) else {
|
|
570
|
+
continue;
|
|
571
|
+
};
|
|
572
|
+
decl_id
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
let Some(Declaration::Namespace(namespace)) = self.graph.declarations().get(&owner_id) else {
|
|
576
|
+
continue;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
if let Some(member) = namespace
|
|
580
|
+
.member(&target)
|
|
581
|
+
.and_then(|member_id| self.graph.declarations().get(member_id))
|
|
582
|
+
&& matches!(
|
|
583
|
+
member,
|
|
584
|
+
Declaration::Constant(_)
|
|
585
|
+
| Declaration::ConstantAlias(_)
|
|
586
|
+
| Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_))
|
|
587
|
+
)
|
|
588
|
+
{
|
|
589
|
+
// `add_declaration` deduplicates by fully qualified name, so this appends
|
|
590
|
+
// the visibility definition to the existing constant declaration.
|
|
591
|
+
self.graph.add_declaration(id, member.name().to_string(), |name| {
|
|
592
|
+
Declaration::Constant(Box::new(ConstantDeclaration::new(name, owner_id)))
|
|
593
|
+
});
|
|
594
|
+
} else {
|
|
595
|
+
let diagnostic = Diagnostic::new(
|
|
596
|
+
Rule::UndefinedConstantVisibilityTarget,
|
|
597
|
+
uri_id,
|
|
598
|
+
offset,
|
|
599
|
+
format!(
|
|
600
|
+
"undefined constant `{constant_name}` for visibility change in `{}`",
|
|
601
|
+
namespace.name()
|
|
602
|
+
),
|
|
603
|
+
);
|
|
604
|
+
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
605
|
+
}
|
|
543
606
|
}
|
|
544
607
|
Definition::MethodVisibility(_) => {
|
|
545
608
|
method_visibility_ids.push(id);
|
|
@@ -574,7 +637,7 @@ impl<'a> Resolver<'a> {
|
|
|
574
637
|
let offset = method_visibility.offset().clone();
|
|
575
638
|
let lexical_nesting_id = *method_visibility.lexical_nesting_id();
|
|
576
639
|
|
|
577
|
-
let Some(owner_id) = self.resolve_lexical_owner(lexical_nesting_id) else {
|
|
640
|
+
let Some(owner_id) = self.resolve_lexical_owner(lexical_nesting_id, id) else {
|
|
578
641
|
continue;
|
|
579
642
|
};
|
|
580
643
|
|
|
@@ -640,6 +703,19 @@ impl<'a> Resolver<'a> {
|
|
|
640
703
|
self.graph.extend_work(pending_work);
|
|
641
704
|
}
|
|
642
705
|
|
|
706
|
+
/// Resolves a constant receiver for `handle_remaining_definitions`.
|
|
707
|
+
/// If the receiver name is unresolved, preserve the definition for a later
|
|
708
|
+
/// resolve cycle instead of dropping work during an incremental delete/re-add gap.
|
|
709
|
+
fn resolve_constant_receiver(&mut self, name_id: NameId, id: DefinitionId) -> Option<DeclarationId> {
|
|
710
|
+
match self.graph.names().get(&name_id).unwrap() {
|
|
711
|
+
NameRef::Resolved(resolved) => Some(*resolved.declaration_id()),
|
|
712
|
+
NameRef::Unresolved(_) => {
|
|
713
|
+
self.graph.push_work(Unit::Definition(id));
|
|
714
|
+
None
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
643
719
|
fn create_declaration<F>(
|
|
644
720
|
&mut self,
|
|
645
721
|
str_id: StringId,
|
|
@@ -689,37 +765,62 @@ impl<'a> Resolver<'a> {
|
|
|
689
765
|
}
|
|
690
766
|
|
|
691
767
|
/// Resolves owner from lexical nesting.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
768
|
+
///
|
|
769
|
+
/// If the owner cannot be resolved yet, re-queues the current definition so
|
|
770
|
+
/// a later resolve cycle can retry instead of permanently dropping it.
|
|
771
|
+
fn resolve_lexical_owner(
|
|
772
|
+
&mut self,
|
|
773
|
+
lexical_nesting_id: Option<DefinitionId>,
|
|
774
|
+
definition_id: DefinitionId,
|
|
775
|
+
) -> Option<DeclarationId> {
|
|
776
|
+
let mut current_nesting = lexical_nesting_id;
|
|
696
777
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
778
|
+
let resolved = loop {
|
|
779
|
+
let Some(id) = current_nesting else {
|
|
780
|
+
break Some(*OBJECT_ID);
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// If no declaration exists yet for this definition, walk up the lexical chain.
|
|
784
|
+
// This handles the case where attr_* definitions inside methods are processed
|
|
785
|
+
// before the method definition itself. A SingletonClass with no declaration
|
|
786
|
+
// is an exception: returning the surrounding scope would attach its members to
|
|
787
|
+
// the wrong owner (e.g. `Object`) and never recover, so retry later instead.
|
|
788
|
+
let Some(declaration_id) = self.graph.definition_id_to_declaration_id(id) else {
|
|
789
|
+
let definition = self.graph.definitions().get(&id).unwrap();
|
|
790
|
+
if matches!(definition, Definition::SingletonClass(_)) {
|
|
791
|
+
break None;
|
|
792
|
+
}
|
|
793
|
+
current_nesting = *definition.lexical_nesting_id();
|
|
794
|
+
continue;
|
|
795
|
+
};
|
|
704
796
|
|
|
705
|
-
|
|
797
|
+
let decl = self.graph.declarations().get(declaration_id).unwrap();
|
|
798
|
+
|
|
799
|
+
// If the associated declaration is a namespace that can own things, we found the right owner. Otherwise, we might
|
|
800
|
+
// have found something nested inside something else (like a method), in which case we have to walk up until we find
|
|
801
|
+
// the appropriate owner.
|
|
802
|
+
if matches!(
|
|
803
|
+
decl,
|
|
804
|
+
Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_) | Namespace::SingletonClass(_))
|
|
805
|
+
) {
|
|
806
|
+
break Some(*declaration_id);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if matches!(decl, Declaration::ConstantAlias(_)) {
|
|
810
|
+
// Follow the alias chain to find the target namespace. If the alias is unresolved,
|
|
811
|
+
// the definition cannot be properly owned yet and should be retried later.
|
|
812
|
+
break self.resolve_to_namespace(*declaration_id);
|
|
813
|
+
}
|
|
706
814
|
|
|
707
|
-
// If the associated declaration is a namespace that can own things, we found the right owner. Otherwise, we might
|
|
708
|
-
// have found something nested inside something else (like a method), in which case we have to recurse until we find
|
|
709
|
-
// the appropriate owner
|
|
710
|
-
if matches!(
|
|
711
|
-
decl,
|
|
712
|
-
Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_) | Namespace::SingletonClass(_))
|
|
713
|
-
) {
|
|
714
|
-
Some(*declaration_id)
|
|
715
|
-
} else if matches!(decl, Declaration::ConstantAlias(_)) {
|
|
716
|
-
// Follow the alias chain to find the target namespace. If the alias is unresolved,
|
|
717
|
-
// the definition cannot be properly owned and should be skipped by the caller.
|
|
718
|
-
self.resolve_to_namespace(*declaration_id)
|
|
719
|
-
} else {
|
|
720
815
|
let definition = self.graph.definitions().get(&id).unwrap();
|
|
721
|
-
|
|
816
|
+
current_nesting = *definition.lexical_nesting_id();
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
if resolved.is_none() {
|
|
820
|
+
self.graph.push_work(Unit::Definition(definition_id));
|
|
722
821
|
}
|
|
822
|
+
|
|
823
|
+
resolved
|
|
723
824
|
}
|
|
724
825
|
|
|
725
826
|
/// Gets or creates a singleton class declaration for a given class/module declaration. For class `Foo`, this
|
|
@@ -1124,11 +1225,17 @@ impl<'a> Resolver<'a> {
|
|
|
1124
1225
|
let name_ref = self.graph.names().get(&name_id).unwrap();
|
|
1125
1226
|
let str_id = *name_ref.str();
|
|
1126
1227
|
|
|
1127
|
-
let outcome = match self.name_owner_id(name_id) {
|
|
1228
|
+
let outcome = match self.name_owner_id(name_id, singleton) {
|
|
1128
1229
|
// name_owner_id returns Unresolved(None) only when the parent scope is genuinely unknown
|
|
1129
1230
|
// (e.g., `class A::B::C` where `A` doesn't exist). This definition needs an owner, so
|
|
1130
1231
|
// create Todo placeholders for the missing parent chain. Todos get promoted when real
|
|
1131
1232
|
// definitions appear later.
|
|
1233
|
+
//
|
|
1234
|
+
// Singleton classes are the exception: `class << UndefinedReceiver` attaches via
|
|
1235
|
+
// `set_singleton_class_id`, not `add_member`, so a TODO receiver would never gain a
|
|
1236
|
+
// member. Emit Retry so the unit is preserved for a later resolve where the receiver
|
|
1237
|
+
// may exist.
|
|
1238
|
+
Outcome::Unresolved(None) if singleton => Outcome::Retry(None),
|
|
1132
1239
|
Outcome::Unresolved(None) => Outcome::Resolved(self.create_todo_for_parent(name_id), None),
|
|
1133
1240
|
other => other,
|
|
1134
1241
|
};
|
|
@@ -1200,7 +1307,11 @@ impl<'a> Resolver<'a> {
|
|
|
1200
1307
|
// Returns the owner declaration ID for a given name. If the name is simple and has no parent scope, then the owner is
|
|
1201
1308
|
// either the nesting or Object. If the name has a parent scope, we attempt to resolve the reference and that should be
|
|
1202
1309
|
// the name's owner. For aliases, resolves through to get the actual namespace.
|
|
1203
|
-
|
|
1310
|
+
//
|
|
1311
|
+
// When `preserve_retry` is true, Retry from resolve_constant_internal is NOT folded into
|
|
1312
|
+
// Unresolved(None). This is used by the singleton path so the unit can retry when the
|
|
1313
|
+
// receiver might resolve later rather than being dropped.
|
|
1314
|
+
fn name_owner_id(&mut self, name_id: NameId, preserve_retry: bool) -> Outcome {
|
|
1204
1315
|
let name_ref = self.graph.names().get(&name_id).unwrap();
|
|
1205
1316
|
|
|
1206
1317
|
if let Some(&parent_scope) = name_ref.parent_scope().as_ref() {
|
|
@@ -1210,7 +1321,8 @@ impl<'a> Resolver<'a> {
|
|
|
1210
1321
|
Outcome::Resolved(id, linearization) => self.resolve_to_primary_namespace(id, linearization),
|
|
1211
1322
|
// The parent scope is genuinely unknown — not a circular alias or pending
|
|
1212
1323
|
// linearization, but a name that doesn't exist anywhere in the graph.
|
|
1213
|
-
Outcome::
|
|
1324
|
+
Outcome::Unresolved(None) => Outcome::Unresolved(None),
|
|
1325
|
+
Outcome::Retry(None) if !preserve_retry => Outcome::Unresolved(None),
|
|
1214
1326
|
other => other,
|
|
1215
1327
|
}
|
|
1216
1328
|
} else if let Some(nesting_id) = name_ref.nesting()
|
|
@@ -1251,7 +1363,7 @@ impl<'a> Resolver<'a> {
|
|
|
1251
1363
|
// Object so it becomes top-level `A`. This way `module A; end` appearing later
|
|
1252
1364
|
// promotes it correctly. Using nesting would incorrectly create `SomeModule::A`.
|
|
1253
1365
|
let parent_owner_id = if parent_has_parent_scope {
|
|
1254
|
-
match self.name_owner_id(parent_scope) {
|
|
1366
|
+
match self.name_owner_id(parent_scope, false) {
|
|
1255
1367
|
Outcome::Resolved(id, _) => id,
|
|
1256
1368
|
_ => self.create_todo_for_parent(parent_scope),
|
|
1257
1369
|
}
|
|
@@ -5128,4 +5128,182 @@ mod visibility_resolution_tests {
|
|
|
5128
5128
|
assert_owner_eq!(context, "Child#foo()", "Child");
|
|
5129
5129
|
assert_visibility_eq!(context, "Child#foo()", Visibility::Private);
|
|
5130
5130
|
}
|
|
5131
|
+
|
|
5132
|
+
#[test]
|
|
5133
|
+
fn retroactive_constant_visibility_on_direct_member() {
|
|
5134
|
+
let mut context = GraphTest::new();
|
|
5135
|
+
context.index_uri(
|
|
5136
|
+
"file:///foo.rb",
|
|
5137
|
+
r"
|
|
5138
|
+
class Foo
|
|
5139
|
+
BAR = 1
|
|
5140
|
+
private_constant :BAR
|
|
5141
|
+
|
|
5142
|
+
BAZ = 2
|
|
5143
|
+
public_constant :BAZ
|
|
5144
|
+
|
|
5145
|
+
QUX = 3
|
|
5146
|
+
|
|
5147
|
+
class Inner; end
|
|
5148
|
+
private_constant :Inner
|
|
5149
|
+
|
|
5150
|
+
module InnerMod; end
|
|
5151
|
+
private_constant :InnerMod
|
|
5152
|
+
end
|
|
5153
|
+
",
|
|
5154
|
+
);
|
|
5155
|
+
context.resolve();
|
|
5156
|
+
|
|
5157
|
+
assert_no_diagnostics!(&context);
|
|
5158
|
+
assert_visibility_eq!(context, "Foo::BAR", Visibility::Private);
|
|
5159
|
+
assert_visibility_eq!(context, "Foo::BAZ", Visibility::Public);
|
|
5160
|
+
assert_visibility_eq!(context, "Foo::QUX", Visibility::Public);
|
|
5161
|
+
assert_visibility_eq!(context, "Foo::Inner", Visibility::Private);
|
|
5162
|
+
assert_visibility_eq!(context, "Foo::InnerMod", Visibility::Private);
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
#[test]
|
|
5166
|
+
fn retroactive_constant_visibility_via_qualified_receiver() {
|
|
5167
|
+
let mut context = GraphTest::new();
|
|
5168
|
+
context.index_uri(
|
|
5169
|
+
"file:///foo.rb",
|
|
5170
|
+
r"
|
|
5171
|
+
class Foo
|
|
5172
|
+
BAR = 1
|
|
5173
|
+
BAZ = 2
|
|
5174
|
+
end
|
|
5175
|
+
|
|
5176
|
+
ALIAS = Foo
|
|
5177
|
+
Foo.private_constant :BAR
|
|
5178
|
+
ALIAS.private_constant :BAZ
|
|
5179
|
+
",
|
|
5180
|
+
);
|
|
5181
|
+
context.resolve();
|
|
5182
|
+
|
|
5183
|
+
assert_no_diagnostics!(&context);
|
|
5184
|
+
assert_visibility_eq!(context, "Foo::BAR", Visibility::Private);
|
|
5185
|
+
assert_visibility_eq!(context, "Foo::BAZ", Visibility::Private);
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5188
|
+
#[test]
|
|
5189
|
+
fn retroactive_constant_visibility_multi_arg_undefined_emits_per_name_diagnostic() {
|
|
5190
|
+
let mut context = GraphTest::new();
|
|
5191
|
+
context.index_uri(
|
|
5192
|
+
"file:///foo.rb",
|
|
5193
|
+
r"
|
|
5194
|
+
class Foo
|
|
5195
|
+
private_constant :NOPE_ONE, :NOPE_TWO
|
|
5196
|
+
end
|
|
5197
|
+
",
|
|
5198
|
+
);
|
|
5199
|
+
context.resolve();
|
|
5200
|
+
|
|
5201
|
+
assert_diagnostics_eq!(
|
|
5202
|
+
context,
|
|
5203
|
+
&[
|
|
5204
|
+
"undefined-constant-visibility-target: undefined constant `NOPE_ONE` for visibility change in `Foo` (2:21-2:29)",
|
|
5205
|
+
"undefined-constant-visibility-target: undefined constant `NOPE_TWO` for visibility change in `Foo` (2:32-2:40)",
|
|
5206
|
+
]
|
|
5207
|
+
);
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
#[test]
|
|
5211
|
+
fn retroactive_constant_visibility_inherited_constant_emits_diagnostic() {
|
|
5212
|
+
let mut context = GraphTest::new();
|
|
5213
|
+
context.index_uri(
|
|
5214
|
+
"file:///foo.rb",
|
|
5215
|
+
r"
|
|
5216
|
+
class Parent
|
|
5217
|
+
CONST = 1
|
|
5218
|
+
end
|
|
5219
|
+
|
|
5220
|
+
class Child < Parent
|
|
5221
|
+
private_constant :CONST
|
|
5222
|
+
end
|
|
5223
|
+
",
|
|
5224
|
+
);
|
|
5225
|
+
context.resolve();
|
|
5226
|
+
|
|
5227
|
+
assert_diagnostics_eq!(
|
|
5228
|
+
context,
|
|
5229
|
+
&[
|
|
5230
|
+
"undefined-constant-visibility-target: undefined constant `CONST` for visibility change in `Child` (6:21-6:26)"
|
|
5231
|
+
]
|
|
5232
|
+
);
|
|
5233
|
+
assert_visibility_eq!(context, "Parent::CONST", Visibility::Public);
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
#[test]
|
|
5237
|
+
fn retroactive_constant_visibility_clears_when_call_removed() {
|
|
5238
|
+
let mut context = GraphTest::new();
|
|
5239
|
+
context.index_uri(
|
|
5240
|
+
"file:///foo.rb",
|
|
5241
|
+
r"
|
|
5242
|
+
class Foo
|
|
5243
|
+
BAR = 1
|
|
5244
|
+
end
|
|
5245
|
+
",
|
|
5246
|
+
);
|
|
5247
|
+
context.index_uri(
|
|
5248
|
+
"file:///vis.rb",
|
|
5249
|
+
r"
|
|
5250
|
+
Foo.private_constant :BAR
|
|
5251
|
+
",
|
|
5252
|
+
);
|
|
5253
|
+
context.resolve();
|
|
5254
|
+
|
|
5255
|
+
assert_no_diagnostics!(&context);
|
|
5256
|
+
assert_visibility_eq!(context, "Foo::BAR", Visibility::Private);
|
|
5257
|
+
|
|
5258
|
+
context.delete_uri("file:///vis.rb");
|
|
5259
|
+
context.resolve();
|
|
5260
|
+
|
|
5261
|
+
assert_no_diagnostics!(&context);
|
|
5262
|
+
assert_visibility_eq!(context, "Foo::BAR", Visibility::Public);
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
#[test]
|
|
5266
|
+
fn retroactive_constant_visibility_inside_singleton_class_body() {
|
|
5267
|
+
let mut context = GraphTest::new();
|
|
5268
|
+
context.index_uri(
|
|
5269
|
+
"file:///foo.rb",
|
|
5270
|
+
r"
|
|
5271
|
+
class Foo
|
|
5272
|
+
class << self
|
|
5273
|
+
BAR = 1
|
|
5274
|
+
private_constant :BAR
|
|
5275
|
+
end
|
|
5276
|
+
end
|
|
5277
|
+
",
|
|
5278
|
+
);
|
|
5279
|
+
context.resolve();
|
|
5280
|
+
|
|
5281
|
+
assert_no_diagnostics!(&context);
|
|
5282
|
+
assert_visibility_eq!(context, "Foo::<Foo>::BAR", Visibility::Private);
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
#[test]
|
|
5286
|
+
fn retroactive_constant_visibility_persists_across_reopened_class() {
|
|
5287
|
+
let mut context = GraphTest::new();
|
|
5288
|
+
context.index_uri(
|
|
5289
|
+
"file:///a.rb",
|
|
5290
|
+
r"
|
|
5291
|
+
class Foo
|
|
5292
|
+
BAR = 1
|
|
5293
|
+
private_constant :BAR
|
|
5294
|
+
end
|
|
5295
|
+
",
|
|
5296
|
+
);
|
|
5297
|
+
context.index_uri(
|
|
5298
|
+
"file:///b.rb",
|
|
5299
|
+
r"
|
|
5300
|
+
class Foo
|
|
5301
|
+
BAR = 2
|
|
5302
|
+
end
|
|
5303
|
+
",
|
|
5304
|
+
);
|
|
5305
|
+
context.resolve();
|
|
5306
|
+
|
|
5307
|
+
assert_visibility_eq!(context, "Foo::BAR", Visibility::Private);
|
|
5308
|
+
}
|
|
5131
5309
|
}
|
|
@@ -64,6 +64,25 @@ impl CDeclaration {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/// Convert a nullable C string to `Option<DeclarationId>`.
|
|
68
|
+
/// Null, empty, or non-UTF-8 input yields `None`.
|
|
69
|
+
///
|
|
70
|
+
/// # Safety
|
|
71
|
+
///
|
|
72
|
+
/// If non-null, `ptr` must point to a valid, NUL-terminated C string that remains valid for the
|
|
73
|
+
/// duration of the call. The contents do not need to be UTF-8 — non-UTF-8 input is handled by returning
|
|
74
|
+
/// `None`.
|
|
75
|
+
pub(crate) unsafe fn decl_id_from_char_ptr(ptr: *const c_char) -> Option<DeclarationId> {
|
|
76
|
+
if ptr.is_null() {
|
|
77
|
+
return None;
|
|
78
|
+
}
|
|
79
|
+
let s = unsafe { utils::convert_char_ptr_to_string(ptr) }.ok()?;
|
|
80
|
+
if s.is_empty() {
|
|
81
|
+
return None;
|
|
82
|
+
}
|
|
83
|
+
Some(DeclarationId::from(s.as_str()))
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
/// An iterator over declaration IDs
|
|
68
87
|
///
|
|
69
88
|
/// We snapshot the IDs at iterator creation so if the graph is modified, the iterator will not see the changes
|