rubydex 0.2.1-aarch64-linux → 0.2.2-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/definition.c +27 -0
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/signature.c +83 -0
- data/ext/rubydex/signature.h +23 -0
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/signature.rb +130 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +1 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +73 -10
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +2818 -2656
- data/rust/rubydex/src/model/definitions.rs +23 -0
- data/rust/rubydex/src/model/graph.rs +40 -12
- data/rust/rubydex/src/query.rs +598 -1
- data/rust/rubydex/src/resolution.rs +30 -11
- data/rust/rubydex/src/resolution_tests.rs +155 -1
- data/rust/rubydex-sys/src/declaration_api.rs +10 -33
- data/rust/rubydex-sys/src/lib.rs +1 -0
- data/rust/rubydex-sys/src/signature_api.rs +209 -0
- metadata +6 -2
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -10,7 +10,7 @@ use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
|
10
10
|
use crate::model::definitions::{Definition, Parameter};
|
|
11
11
|
use crate::model::graph::Graph;
|
|
12
12
|
use crate::model::identity_maps::IdentityHashSet;
|
|
13
|
-
use crate::model::ids::{DeclarationId, NameId, StringId, UriId};
|
|
13
|
+
use crate::model::ids::{DeclarationId, DefinitionId, NameId, StringId, UriId};
|
|
14
14
|
use crate::model::keywords::{self, Keyword};
|
|
15
15
|
use crate::model::name::NameRef;
|
|
16
16
|
use crate::model::visibility::Visibility;
|
|
@@ -674,6 +674,153 @@ fn method_argument_completion<'a>(
|
|
|
674
674
|
Ok(candidates)
|
|
675
675
|
}
|
|
676
676
|
|
|
677
|
+
/// Reasons [`find_member_in_ancestors`] could not produce a target declaration.
|
|
678
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
679
|
+
pub enum FindMemberError {
|
|
680
|
+
/// The provided declaration id does not exist in the graph.
|
|
681
|
+
DeclarationNotFound,
|
|
682
|
+
/// The declaration exists but is not a namespace, so it has no members or ancestor chain to search.
|
|
683
|
+
NotNamespace,
|
|
684
|
+
/// The declaration is a namespace, but no matching member exists on it or any of its ancestors.
|
|
685
|
+
MemberNotFound,
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/// Finds the given member on the ancestor chain of the declaration. Use `only_inherited` to skip all ancestors until
|
|
689
|
+
/// the main namespace and start from its parent.
|
|
690
|
+
///
|
|
691
|
+
/// # Errors
|
|
692
|
+
///
|
|
693
|
+
/// Returns a [`FindMemberError`] describing why no target declaration could be produced (declaration not found, not a
|
|
694
|
+
/// namespace, or member missing on the ancestor chain).
|
|
695
|
+
///
|
|
696
|
+
/// # Panics
|
|
697
|
+
///
|
|
698
|
+
/// Will panic if we incorrectly store ancestors that are not namespaces.
|
|
699
|
+
pub fn find_member_in_ancestors(
|
|
700
|
+
graph: &Graph,
|
|
701
|
+
declaration_id: DeclarationId,
|
|
702
|
+
member_str_id: StringId,
|
|
703
|
+
only_inherited: bool,
|
|
704
|
+
) -> Result<DeclarationId, FindMemberError> {
|
|
705
|
+
let declaration = graph
|
|
706
|
+
.declarations()
|
|
707
|
+
.get(&declaration_id)
|
|
708
|
+
.ok_or(FindMemberError::DeclarationNotFound)?;
|
|
709
|
+
let namespace = declaration.as_namespace().ok_or(FindMemberError::NotNamespace)?;
|
|
710
|
+
let mut found_main_namespace = false;
|
|
711
|
+
|
|
712
|
+
for ancestor in namespace.ancestors() {
|
|
713
|
+
let Ancestor::Complete(ancestor_id) = ancestor else {
|
|
714
|
+
continue;
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
if only_inherited && !found_main_namespace {
|
|
718
|
+
if *ancestor_id == declaration_id {
|
|
719
|
+
found_main_namespace = true;
|
|
720
|
+
}
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if let Some(member_id) = graph
|
|
725
|
+
.declarations()
|
|
726
|
+
.get(ancestor_id)
|
|
727
|
+
.unwrap()
|
|
728
|
+
.as_namespace()
|
|
729
|
+
.unwrap()
|
|
730
|
+
.member(&member_str_id)
|
|
731
|
+
{
|
|
732
|
+
return Ok(*member_id);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
Err(FindMemberError::MemberNotFound)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/// Reasons [`follow_method_alias`] could not produce a target declaration.
|
|
740
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
741
|
+
pub enum AliasResolutionError {
|
|
742
|
+
/// The provided definition id is not a `MethodAlias`.
|
|
743
|
+
NotAnAlias,
|
|
744
|
+
/// The alias's owner could not be resolved (e.g., a `ConstantReceiver` whose name never resolved to a declaration,
|
|
745
|
+
/// or a singleton-class chain whose attached object isn't resolvable).
|
|
746
|
+
UnresolvedOwner,
|
|
747
|
+
/// The chain of aliases forms a cycle. The chain was abandoned at the first revisit.
|
|
748
|
+
Cycle,
|
|
749
|
+
/// The alias's `old_name` does not exist on the owner or any of its ancestors.
|
|
750
|
+
TargetNotFound,
|
|
751
|
+
/// The resolved target is not a method declaration. Indicates a graph inconsistency since method-name lookups
|
|
752
|
+
/// should only land on `Declaration::Method`.
|
|
753
|
+
TargetNotMethod,
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/// Follows `alias_id` through any chain of further `MethodAlias` definitions and returns the `DeclarationId` of the
|
|
757
|
+
/// final method declaration that has at least one non-alias definition (a regular `def`, `attr_*`, etc.).
|
|
758
|
+
///
|
|
759
|
+
/// # Errors
|
|
760
|
+
///
|
|
761
|
+
/// Returns an `AliasResolutionError` describing why the chain could not be resolved (not an alias, unresolved owner,
|
|
762
|
+
/// cyclic chain, target missing, or target not a method).
|
|
763
|
+
///
|
|
764
|
+
/// # Panics
|
|
765
|
+
///
|
|
766
|
+
/// Panics if the graph is internally inconsistent
|
|
767
|
+
pub fn follow_method_alias(graph: &Graph, alias_id: DefinitionId) -> Result<DeclarationId, AliasResolutionError> {
|
|
768
|
+
let mut seen: IdentityHashSet<DeclarationId> = IdentityHashSet::default();
|
|
769
|
+
let mut current = alias_id;
|
|
770
|
+
|
|
771
|
+
loop {
|
|
772
|
+
let Some(Definition::MethodAlias(alias)) = graph.definitions().get(¤t) else {
|
|
773
|
+
return Err(AliasResolutionError::NotAnAlias);
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
let owner_id = graph
|
|
777
|
+
.definition_id_to_declaration_id(current)
|
|
778
|
+
.and_then(|decl_id| graph.declarations().get(decl_id))
|
|
779
|
+
.map(|decl| *decl.owner_id())
|
|
780
|
+
.ok_or(AliasResolutionError::UnresolvedOwner)?;
|
|
781
|
+
|
|
782
|
+
let target_id = match find_member_in_ancestors(graph, owner_id, *alias.old_name_str_id(), false) {
|
|
783
|
+
Ok(id) => id,
|
|
784
|
+
Err(FindMemberError::MemberNotFound) => return Err(AliasResolutionError::TargetNotFound),
|
|
785
|
+
Err(err @ (FindMemberError::DeclarationNotFound | FindMemberError::NotNamespace)) => {
|
|
786
|
+
unreachable!("alias owner must be a valid namespace declaration, got {err:?}")
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if !seen.insert(target_id) {
|
|
791
|
+
return Err(AliasResolutionError::Cycle);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
let Declaration::Method(target) = graph
|
|
795
|
+
.declarations()
|
|
796
|
+
.get(&target_id)
|
|
797
|
+
.expect("member returned by find_member_in_ancestors must exist")
|
|
798
|
+
else {
|
|
799
|
+
return Err(AliasResolutionError::TargetNotMethod);
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Stop at the first non-alias definition; otherwise track the smallest alias `DefinitionId` so the trace stays
|
|
803
|
+
// deterministic across runs. (If two aliases target different methods, we just pick one of them.)
|
|
804
|
+
let mut maybe_next_alias: Option<DefinitionId> = None;
|
|
805
|
+
|
|
806
|
+
for &def_id in target.definitions() {
|
|
807
|
+
if !matches!(
|
|
808
|
+
graph
|
|
809
|
+
.definitions()
|
|
810
|
+
.get(&def_id)
|
|
811
|
+
.expect("declaration definition_id must exist in the graph"),
|
|
812
|
+
Definition::MethodAlias(_),
|
|
813
|
+
) {
|
|
814
|
+
return Ok(target_id);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
maybe_next_alias = Some(maybe_next_alias.map_or(def_id, |m| m.min(def_id)));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
current = maybe_next_alias.ok_or(AliasResolutionError::TargetNotFound)?;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
677
824
|
#[cfg(test)]
|
|
678
825
|
mod tests {
|
|
679
826
|
use std::str::FromStr;
|
|
@@ -3154,4 +3301,454 @@ mod tests {
|
|
|
3154
3301
|
]
|
|
3155
3302
|
);
|
|
3156
3303
|
}
|
|
3304
|
+
|
|
3305
|
+
/// Returns the smallest `MethodAlias` `DefinitionId` for the declaration named `alias_decl_fqn`
|
|
3306
|
+
/// (e.g., `"Foo#aliased()"`). Picking the smallest mirrors `follow_method_alias`'s own
|
|
3307
|
+
/// determinism rule for tests where multiple aliases share a declaration (e.g. cross-file fixtures).
|
|
3308
|
+
fn alias_def_id(context: &GraphTest, alias_decl_fqn: &str) -> DefinitionId {
|
|
3309
|
+
let decl = context
|
|
3310
|
+
.graph()
|
|
3311
|
+
.declarations()
|
|
3312
|
+
.get(&DeclarationId::from(alias_decl_fqn))
|
|
3313
|
+
.unwrap_or_else(|| panic!("expected declaration {alias_decl_fqn}"));
|
|
3314
|
+
|
|
3315
|
+
decl.definitions()
|
|
3316
|
+
.iter()
|
|
3317
|
+
.copied()
|
|
3318
|
+
.filter(|def_id| {
|
|
3319
|
+
matches!(
|
|
3320
|
+
context.graph().definitions().get(def_id),
|
|
3321
|
+
Some(Definition::MethodAlias(_)),
|
|
3322
|
+
)
|
|
3323
|
+
})
|
|
3324
|
+
.min()
|
|
3325
|
+
.unwrap_or_else(|| panic!("declaration {alias_decl_fqn} has no MethodAlias definition"))
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
/// Asserts that the alias declared as `$alias_fqn` follows to the declaration `$target_fqn`.
|
|
3329
|
+
macro_rules! assert_alias_target {
|
|
3330
|
+
($context:expr, $alias_fqn:expr, $target_fqn:expr $(,)?) => {{
|
|
3331
|
+
let context = $context;
|
|
3332
|
+
assert_eq!(
|
|
3333
|
+
follow_method_alias(context.graph(), alias_def_id(context, $alias_fqn)),
|
|
3334
|
+
Ok(DeclarationId::from($target_fqn)),
|
|
3335
|
+
);
|
|
3336
|
+
}};
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
#[test]
|
|
3340
|
+
fn follow_method_alias_to_local_method() {
|
|
3341
|
+
let mut context = GraphTest::new();
|
|
3342
|
+
context.index_uri(
|
|
3343
|
+
"file:///foo.rb",
|
|
3344
|
+
r"
|
|
3345
|
+
class Foo
|
|
3346
|
+
def original; end
|
|
3347
|
+
alias aliased original
|
|
3348
|
+
end
|
|
3349
|
+
",
|
|
3350
|
+
);
|
|
3351
|
+
context.resolve();
|
|
3352
|
+
|
|
3353
|
+
assert_alias_target!(&context, "Foo#aliased()", "Foo#original()");
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
#[test]
|
|
3357
|
+
fn follow_method_alias_through_chain_of_aliases() {
|
|
3358
|
+
let mut context = GraphTest::new();
|
|
3359
|
+
context.index_uri(
|
|
3360
|
+
"file:///foo.rb",
|
|
3361
|
+
r"
|
|
3362
|
+
class Foo
|
|
3363
|
+
def real; end
|
|
3364
|
+
alias mid real
|
|
3365
|
+
alias outer mid
|
|
3366
|
+
end
|
|
3367
|
+
",
|
|
3368
|
+
);
|
|
3369
|
+
context.resolve();
|
|
3370
|
+
|
|
3371
|
+
assert_alias_target!(&context, "Foo#outer()", "Foo#real()");
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
#[test]
|
|
3375
|
+
fn follow_method_alias_detects_two_step_cycle() {
|
|
3376
|
+
let mut context = GraphTest::new();
|
|
3377
|
+
context.index_uri(
|
|
3378
|
+
"file:///foo.rb",
|
|
3379
|
+
r"
|
|
3380
|
+
class Foo
|
|
3381
|
+
alias a b
|
|
3382
|
+
alias b a
|
|
3383
|
+
end
|
|
3384
|
+
",
|
|
3385
|
+
);
|
|
3386
|
+
context.resolve();
|
|
3387
|
+
|
|
3388
|
+
assert_eq!(
|
|
3389
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#a()")),
|
|
3390
|
+
Err(AliasResolutionError::Cycle),
|
|
3391
|
+
);
|
|
3392
|
+
assert_eq!(
|
|
3393
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#b()")),
|
|
3394
|
+
Err(AliasResolutionError::Cycle),
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
#[test]
|
|
3399
|
+
fn follow_method_alias_detects_multi_step_cycle() {
|
|
3400
|
+
let mut context = GraphTest::new();
|
|
3401
|
+
context.index_uri(
|
|
3402
|
+
"file:///foo.rb",
|
|
3403
|
+
r"
|
|
3404
|
+
class Foo
|
|
3405
|
+
alias a b
|
|
3406
|
+
alias b c
|
|
3407
|
+
alias c a
|
|
3408
|
+
end
|
|
3409
|
+
",
|
|
3410
|
+
);
|
|
3411
|
+
context.resolve();
|
|
3412
|
+
|
|
3413
|
+
for alias_fqn in ["Foo#a()", "Foo#b()", "Foo#c()"] {
|
|
3414
|
+
assert_eq!(
|
|
3415
|
+
follow_method_alias(context.graph(), alias_def_id(&context, alias_fqn)),
|
|
3416
|
+
Err(AliasResolutionError::Cycle),
|
|
3417
|
+
"expected {alias_fqn} to be detected as part of the cycle",
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
#[test]
|
|
3423
|
+
fn follow_method_alias_detects_self_cycle() {
|
|
3424
|
+
let mut context = GraphTest::new();
|
|
3425
|
+
context.index_uri(
|
|
3426
|
+
"file:///foo.rb",
|
|
3427
|
+
r"
|
|
3428
|
+
class Foo
|
|
3429
|
+
alias foo foo
|
|
3430
|
+
end
|
|
3431
|
+
",
|
|
3432
|
+
);
|
|
3433
|
+
context.resolve();
|
|
3434
|
+
|
|
3435
|
+
assert_eq!(
|
|
3436
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#foo()")),
|
|
3437
|
+
Err(AliasResolutionError::Cycle),
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
#[test]
|
|
3442
|
+
fn follow_method_alias_to_inherited_method() {
|
|
3443
|
+
let mut context = GraphTest::new();
|
|
3444
|
+
context.index_uri(
|
|
3445
|
+
"file:///foo.rb",
|
|
3446
|
+
r"
|
|
3447
|
+
class Parent
|
|
3448
|
+
def inherited_m; end
|
|
3449
|
+
end
|
|
3450
|
+
|
|
3451
|
+
class Child < Parent
|
|
3452
|
+
alias aliased inherited_m
|
|
3453
|
+
end
|
|
3454
|
+
",
|
|
3455
|
+
);
|
|
3456
|
+
context.resolve();
|
|
3457
|
+
|
|
3458
|
+
assert_alias_target!(&context, "Child#aliased()", "Parent#inherited_m()");
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
#[test]
|
|
3462
|
+
fn follow_method_alias_with_constant_receiver() {
|
|
3463
|
+
let mut context = GraphTest::new();
|
|
3464
|
+
context.index_uri(
|
|
3465
|
+
"file:///foo.rb",
|
|
3466
|
+
r"
|
|
3467
|
+
class Bar
|
|
3468
|
+
def to_s; end
|
|
3469
|
+
end
|
|
3470
|
+
|
|
3471
|
+
class Foo
|
|
3472
|
+
Bar.alias_method(:new_to_s, :to_s)
|
|
3473
|
+
end
|
|
3474
|
+
",
|
|
3475
|
+
);
|
|
3476
|
+
context.resolve();
|
|
3477
|
+
|
|
3478
|
+
assert_alias_target!(&context, "Bar#new_to_s()", "Bar#to_s()");
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
#[test]
|
|
3482
|
+
fn follow_method_alias_in_singleton_class_body() {
|
|
3483
|
+
let mut context = GraphTest::new();
|
|
3484
|
+
context.index_uri(
|
|
3485
|
+
"file:///foo.rb",
|
|
3486
|
+
r"
|
|
3487
|
+
class Foo
|
|
3488
|
+
def self.find; end
|
|
3489
|
+
|
|
3490
|
+
class << self
|
|
3491
|
+
alias_method :find_old, :find
|
|
3492
|
+
end
|
|
3493
|
+
end
|
|
3494
|
+
",
|
|
3495
|
+
);
|
|
3496
|
+
context.resolve();
|
|
3497
|
+
|
|
3498
|
+
assert_alias_target!(&context, "Foo::<Foo>#find_old()", "Foo::<Foo>#find()");
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
#[test]
|
|
3502
|
+
fn follow_method_alias_in_singleton_class_body_misses_instance_method() {
|
|
3503
|
+
let mut context = GraphTest::new();
|
|
3504
|
+
context.index_uri(
|
|
3505
|
+
"file:///foo.rb",
|
|
3506
|
+
r"
|
|
3507
|
+
class Foo
|
|
3508
|
+
def regular; end
|
|
3509
|
+
|
|
3510
|
+
class << self
|
|
3511
|
+
alias_method :other, :regular
|
|
3512
|
+
end
|
|
3513
|
+
end
|
|
3514
|
+
",
|
|
3515
|
+
);
|
|
3516
|
+
context.resolve();
|
|
3517
|
+
|
|
3518
|
+
assert_eq!(
|
|
3519
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo::<Foo>#other()")),
|
|
3520
|
+
Err(AliasResolutionError::TargetNotFound),
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
#[test]
|
|
3525
|
+
fn follow_method_alias_to_attr_reader() {
|
|
3526
|
+
let mut context = GraphTest::new();
|
|
3527
|
+
context.index_uri(
|
|
3528
|
+
"file:///foo.rb",
|
|
3529
|
+
r"
|
|
3530
|
+
class Foo
|
|
3531
|
+
attr_reader :name
|
|
3532
|
+
alias display name
|
|
3533
|
+
end
|
|
3534
|
+
",
|
|
3535
|
+
);
|
|
3536
|
+
context.resolve();
|
|
3537
|
+
|
|
3538
|
+
assert_alias_target!(&context, "Foo#display()", "Foo#name()");
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
#[test]
|
|
3542
|
+
fn follow_method_alias_to_attr_accessor() {
|
|
3543
|
+
let mut context = GraphTest::new();
|
|
3544
|
+
context.index_uri(
|
|
3545
|
+
"file:///foo.rb",
|
|
3546
|
+
r"
|
|
3547
|
+
class Foo
|
|
3548
|
+
attr_accessor :age
|
|
3549
|
+
alias years age
|
|
3550
|
+
end
|
|
3551
|
+
",
|
|
3552
|
+
);
|
|
3553
|
+
context.resolve();
|
|
3554
|
+
|
|
3555
|
+
assert_alias_target!(&context, "Foo#years()", "Foo#age()");
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
#[test]
|
|
3559
|
+
fn follow_method_alias_to_method_in_prepended_module() {
|
|
3560
|
+
let mut context = GraphTest::new();
|
|
3561
|
+
context.index_uri(
|
|
3562
|
+
"file:///foo.rb",
|
|
3563
|
+
r"
|
|
3564
|
+
module M
|
|
3565
|
+
def original; end
|
|
3566
|
+
end
|
|
3567
|
+
|
|
3568
|
+
class Foo
|
|
3569
|
+
prepend M
|
|
3570
|
+
alias aliased original
|
|
3571
|
+
end
|
|
3572
|
+
",
|
|
3573
|
+
);
|
|
3574
|
+
context.resolve();
|
|
3575
|
+
|
|
3576
|
+
assert_alias_target!(&context, "Foo#aliased()", "M#original()");
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
#[test]
|
|
3580
|
+
fn follow_method_alias_ignores_visibility_of_target() {
|
|
3581
|
+
let mut context = GraphTest::new();
|
|
3582
|
+
context.index_uri(
|
|
3583
|
+
"file:///foo.rb",
|
|
3584
|
+
r"
|
|
3585
|
+
class Foo
|
|
3586
|
+
private def secret; end
|
|
3587
|
+
alias revealed secret
|
|
3588
|
+
end
|
|
3589
|
+
",
|
|
3590
|
+
);
|
|
3591
|
+
context.resolve();
|
|
3592
|
+
|
|
3593
|
+
assert_alias_target!(&context, "Foo#revealed()", "Foo#secret()");
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
#[test]
|
|
3597
|
+
fn follow_method_alias_picks_last_when_multiple_targets() {
|
|
3598
|
+
let mut context = GraphTest::new();
|
|
3599
|
+
context.index_uri(
|
|
3600
|
+
"file:///foo.rb",
|
|
3601
|
+
r"
|
|
3602
|
+
class Foo
|
|
3603
|
+
def bar; end
|
|
3604
|
+
def qux; end
|
|
3605
|
+
alias double bar
|
|
3606
|
+
alias double qux
|
|
3607
|
+
end
|
|
3608
|
+
",
|
|
3609
|
+
);
|
|
3610
|
+
context.resolve();
|
|
3611
|
+
|
|
3612
|
+
assert_alias_target!(&context, "Foo#double()", "Foo#qux()");
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
#[test]
|
|
3616
|
+
fn follow_method_alias_returns_target_not_found_when_target_missing() {
|
|
3617
|
+
let mut context = GraphTest::new();
|
|
3618
|
+
context.index_uri(
|
|
3619
|
+
"file:///foo.rb",
|
|
3620
|
+
r"
|
|
3621
|
+
class Foo
|
|
3622
|
+
alias aliased nonexistent
|
|
3623
|
+
end
|
|
3624
|
+
",
|
|
3625
|
+
);
|
|
3626
|
+
context.resolve();
|
|
3627
|
+
|
|
3628
|
+
assert_eq!(
|
|
3629
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#aliased()")),
|
|
3630
|
+
Err(AliasResolutionError::TargetNotFound),
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
#[test]
|
|
3635
|
+
fn find_member_in_ancestors_returns_member_in_main_namespace() {
|
|
3636
|
+
let mut context = GraphTest::new();
|
|
3637
|
+
context.index_uri(
|
|
3638
|
+
"file:///foo.rb",
|
|
3639
|
+
r"
|
|
3640
|
+
class Foo
|
|
3641
|
+
def bar; end
|
|
3642
|
+
end
|
|
3643
|
+
",
|
|
3644
|
+
);
|
|
3645
|
+
context.resolve();
|
|
3646
|
+
|
|
3647
|
+
assert_eq!(
|
|
3648
|
+
find_member_in_ancestors(
|
|
3649
|
+
context.graph(),
|
|
3650
|
+
DeclarationId::from("Foo"),
|
|
3651
|
+
StringId::from("bar()"),
|
|
3652
|
+
false,
|
|
3653
|
+
),
|
|
3654
|
+
Ok(DeclarationId::from("Foo#bar()")),
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
#[test]
|
|
3659
|
+
fn find_member_in_ancestors_returns_inherited_member() {
|
|
3660
|
+
let mut context = GraphTest::new();
|
|
3661
|
+
context.index_uri(
|
|
3662
|
+
"file:///foo.rb",
|
|
3663
|
+
r"
|
|
3664
|
+
class Parent
|
|
3665
|
+
def inherited_method; end
|
|
3666
|
+
end
|
|
3667
|
+
|
|
3668
|
+
class Child < Parent
|
|
3669
|
+
end
|
|
3670
|
+
",
|
|
3671
|
+
);
|
|
3672
|
+
context.resolve();
|
|
3673
|
+
|
|
3674
|
+
assert_eq!(
|
|
3675
|
+
find_member_in_ancestors(
|
|
3676
|
+
context.graph(),
|
|
3677
|
+
DeclarationId::from("Child"),
|
|
3678
|
+
StringId::from("inherited_method()"),
|
|
3679
|
+
false,
|
|
3680
|
+
),
|
|
3681
|
+
Ok(DeclarationId::from("Parent#inherited_method()")),
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
#[test]
|
|
3686
|
+
fn find_member_in_ancestors_returns_member_not_found_when_member_missing() {
|
|
3687
|
+
let mut context = GraphTest::new();
|
|
3688
|
+
context.index_uri(
|
|
3689
|
+
"file:///foo.rb",
|
|
3690
|
+
r"
|
|
3691
|
+
class Foo
|
|
3692
|
+
end
|
|
3693
|
+
",
|
|
3694
|
+
);
|
|
3695
|
+
context.resolve();
|
|
3696
|
+
|
|
3697
|
+
assert_eq!(
|
|
3698
|
+
find_member_in_ancestors(
|
|
3699
|
+
context.graph(),
|
|
3700
|
+
DeclarationId::from("Foo"),
|
|
3701
|
+
StringId::from("missing()"),
|
|
3702
|
+
false,
|
|
3703
|
+
),
|
|
3704
|
+
Err(FindMemberError::MemberNotFound),
|
|
3705
|
+
);
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
#[test]
|
|
3709
|
+
fn find_member_in_ancestors_returns_not_a_namespace_for_method_declaration() {
|
|
3710
|
+
let mut context = GraphTest::new();
|
|
3711
|
+
context.index_uri(
|
|
3712
|
+
"file:///foo.rb",
|
|
3713
|
+
r"
|
|
3714
|
+
class Foo
|
|
3715
|
+
def bar; end
|
|
3716
|
+
end
|
|
3717
|
+
",
|
|
3718
|
+
);
|
|
3719
|
+
context.resolve();
|
|
3720
|
+
|
|
3721
|
+
assert_eq!(
|
|
3722
|
+
find_member_in_ancestors(
|
|
3723
|
+
context.graph(),
|
|
3724
|
+
DeclarationId::from("Foo#bar()"),
|
|
3725
|
+
StringId::from("anything"),
|
|
3726
|
+
false,
|
|
3727
|
+
),
|
|
3728
|
+
Err(FindMemberError::NotNamespace),
|
|
3729
|
+
);
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
#[test]
|
|
3733
|
+
fn find_member_in_ancestors_returns_declaration_not_found_for_unknown_id() {
|
|
3734
|
+
let mut context = GraphTest::new();
|
|
3735
|
+
context.index_uri(
|
|
3736
|
+
"file:///foo.rb",
|
|
3737
|
+
r"
|
|
3738
|
+
class Foo
|
|
3739
|
+
end
|
|
3740
|
+
",
|
|
3741
|
+
);
|
|
3742
|
+
context.resolve();
|
|
3743
|
+
|
|
3744
|
+
assert_eq!(
|
|
3745
|
+
find_member_in_ancestors(
|
|
3746
|
+
context.graph(),
|
|
3747
|
+
DeclarationId::from("DoesNotExist"),
|
|
3748
|
+
StringId::from("anything"),
|
|
3749
|
+
false,
|
|
3750
|
+
),
|
|
3751
|
+
Err(FindMemberError::DeclarationNotFound),
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3157
3754
|
}
|
|
@@ -620,7 +620,8 @@ impl<'a> Resolver<'a> {
|
|
|
620
620
|
self.resolve_method_visibilities(method_visibility_ids);
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
/// Resolves retroactive method visibility changes (`private :foo`, `protected :foo`, `public :foo
|
|
623
|
+
/// Resolves retroactive method visibility changes (`private :foo`, `protected :foo`, `public :foo`,
|
|
624
|
+
/// `private_class_method :foo`, `public_class_method :foo`).
|
|
624
625
|
///
|
|
625
626
|
/// Runs as a second pass after all methods/attrs are declared, so `private :bar` works
|
|
626
627
|
/// regardless of whether `def bar` appeared before or after it in source.
|
|
@@ -636,11 +637,21 @@ impl<'a> Resolver<'a> {
|
|
|
636
637
|
let uri_id = *method_visibility.uri_id();
|
|
637
638
|
let offset = method_visibility.offset().clone();
|
|
638
639
|
let lexical_nesting_id = *method_visibility.lexical_nesting_id();
|
|
640
|
+
let is_singleton = method_visibility.flags().is_singleton_method_visibility();
|
|
639
641
|
|
|
640
|
-
let Some(
|
|
642
|
+
let Some(lexical_owner_id) = self.resolve_lexical_owner(lexical_nesting_id, id) else {
|
|
641
643
|
continue;
|
|
642
644
|
};
|
|
643
645
|
|
|
646
|
+
let owner_id = if is_singleton {
|
|
647
|
+
let Some(singleton_id) = self.get_or_create_singleton_class(lexical_owner_id, true) else {
|
|
648
|
+
continue;
|
|
649
|
+
};
|
|
650
|
+
singleton_id
|
|
651
|
+
} else {
|
|
652
|
+
lexical_owner_id
|
|
653
|
+
};
|
|
654
|
+
|
|
644
655
|
let Some(Declaration::Namespace(namespace)) = self.graph.declarations().get(&owner_id) else {
|
|
645
656
|
continue;
|
|
646
657
|
};
|
|
@@ -689,13 +700,20 @@ impl<'a> Resolver<'a> {
|
|
|
689
700
|
Rule::UndefinedMethodVisibilityTarget,
|
|
690
701
|
uri_id,
|
|
691
702
|
offset,
|
|
692
|
-
format!("undefined method `{method_name}` for visibility change
|
|
703
|
+
format!("undefined method `{owner_name}#{method_name}` for visibility change"),
|
|
693
704
|
);
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
.
|
|
698
|
-
.
|
|
705
|
+
if is_singleton {
|
|
706
|
+
// Document-scoped: the singleton class may be synthetic (created by this
|
|
707
|
+
// visibility resolution) and won't be cleaned up on file delete, so attaching
|
|
708
|
+
// the diagnostic to the declaration would leave it orphaned.
|
|
709
|
+
self.graph.add_document_diagnostic(uri_id, diagnostic);
|
|
710
|
+
} else {
|
|
711
|
+
self.graph
|
|
712
|
+
.declarations_mut()
|
|
713
|
+
.get_mut(&owner_id)
|
|
714
|
+
.unwrap()
|
|
715
|
+
.add_diagnostic(diagnostic);
|
|
716
|
+
}
|
|
699
717
|
}
|
|
700
718
|
}
|
|
701
719
|
|
|
@@ -1880,7 +1898,7 @@ impl<'a> Resolver<'a> {
|
|
|
1880
1898
|
singleton_methods.push(Unit::Definition(id));
|
|
1881
1899
|
}
|
|
1882
1900
|
_ => {
|
|
1883
|
-
others.push(id);
|
|
1901
|
+
others.push((id, (*definition.uri_id(), definition.offset())));
|
|
1884
1902
|
}
|
|
1885
1903
|
}
|
|
1886
1904
|
}
|
|
@@ -1920,14 +1938,15 @@ impl<'a> Resolver<'a> {
|
|
|
1920
1938
|
(depths.get(name_a).unwrap(), uri_a, offset_a).cmp(&(depths.get(name_b).unwrap(), uri_b, offset_b))
|
|
1921
1939
|
});
|
|
1922
1940
|
|
|
1941
|
+
others.sort_unstable_by_key(|(_, key)| *key);
|
|
1942
|
+
|
|
1923
1943
|
// Definitions first, then constant refs, then singleton methods, then ancestors
|
|
1924
1944
|
self.unit_queue.extend(definitions.into_iter().map(|(id, _)| id));
|
|
1925
1945
|
self.unit_queue.extend(const_refs.into_iter().map(|(id, _)| id));
|
|
1926
1946
|
self.unit_queue.extend(singleton_methods);
|
|
1927
1947
|
self.unit_queue.extend(ancestors.into_iter().map(Unit::Ancestors));
|
|
1928
1948
|
|
|
1929
|
-
others.
|
|
1930
|
-
others
|
|
1949
|
+
others.into_iter().map(|(id, _)| id).collect()
|
|
1931
1950
|
}
|
|
1932
1951
|
|
|
1933
1952
|
/// Returns the singleton parent ID for an attached object ID. A singleton class' parent depends on what the attached
|