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.
@@ -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(&current) 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(owner_id) = self.resolve_lexical_owner(lexical_nesting_id, id) else {
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 in `{owner_name}`"),
703
+ format!("undefined method `{owner_name}#{method_name}` for visibility change"),
693
704
  );
694
- self.graph
695
- .declarations_mut()
696
- .get_mut(&owner_id)
697
- .unwrap()
698
- .add_diagnostic(diagnostic);
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.shrink_to_fit();
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