rubydex 0.1.0.beta13 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 922611abae468890ba5fcf77e365aba7ac79754c5db93abeec4ad67fb84469a0
4
- data.tar.gz: 75ccad3efb46e07f582e732452be8d388183ee2030e2569c1a19a07844ab20f8
3
+ metadata.gz: 62ff27bd44d2fb24baaea47fbead5e90a99b129d77fb189e9410ba7a04b6a1df
4
+ data.tar.gz: 9acd6049e9e44db280b8a6665b7ac6772703d28e89c3b016b8ccedd133fc1de6
5
5
  SHA512:
6
- metadata.gz: 4810b81cca5c1aceb7546f1e75f348217ae8b0f2b9559e315b58e96eb8a62ca5603c68ae863da43272830feef452389b390c1b2786cfc3ff0944dc0e1096acb2
7
- data.tar.gz: e119905f38a21842a543053e0218cd29559c17865e81485fd7f54d74f43782f445902b166d1cd20c28dcd4b5bdf39533122f8cff38a81201f6c25868bb78798f
6
+ metadata.gz: b1d3b9c88293608e2cce5e7d30b8553ca02bb9d1026de21a362f93fa6af530f566f2858fe1db38eb8b17f4280b71d5af4deddff07ba09d5068430126c130389e
7
+ data.tar.gz: 9d552f809c4d9a1a71b4301fa5622d2db60bb3fa4aa92e03329c62034483d2225dbd5bc0d87b2202d16e59653bdb742bc13dd831940a1d41607f79def20b6b34
data/exe/rdx CHANGED
@@ -2,11 +2,12 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+
5
6
  require "optparse"
6
7
 
7
- OptionParser.new do |parser|
8
- parser.banner = "Usage: [path1, path2]"
8
+ options = {}
9
9
 
10
+ OptionParser.new do |parser|
10
11
  parser.on("--version", "Print the gem's version") do
11
12
  require "rubydex/version"
12
13
  puts "v#{Rubydex::VERSION}"
@@ -17,31 +18,34 @@ OptionParser.new do |parser|
17
18
  puts parser
18
19
  exit
19
20
  end
21
+
22
+ parser.on("-i", "--interactive", "Open an interactive session with a populated graph for the current workspace") do
23
+ options[:interactive] = true
24
+ end
20
25
  end.parse!
21
26
 
22
27
  require "rubydex"
23
28
 
24
- workspace_path = ARGV.first
25
-
26
- # If a workspace path is passed, we need to ensure that Bundler is setup in that context so that we find the
27
- # dependencies for that project
28
- if workspace_path
29
- gemfile_path = File.join(workspace_path, "Gemfile")
30
-
31
- if File.exist?(gemfile_path)
32
- ENV["BUNDLE_GEMFILE"] = gemfile_path
33
-
34
- begin
35
- Bundler.setup
36
- rescue Bundler::BundlerError => e
37
- $stderr.puts(<<~MESSAGE)
38
- Bundle setup failed for #{gemfile_path}. Indexing of dependencies may be partial
39
- Error:
40
- #{e.message}
41
- MESSAGE
42
- end
43
- end
29
+ def __with_timer(message, &block)
30
+ print(message)
31
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
32
+ block.call
33
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
34
+ puts " finished in #{duration.round(2)}ms"
44
35
  end
45
36
 
46
- graph = Rubydex::Graph.new(workspace_path: workspace_path)
47
- graph.index_workspace
37
+ graph = Rubydex::Graph.new
38
+ __with_timer("Indexing workspace...") { graph.index_workspace }
39
+ __with_timer("Resolving graph...") { graph.resolve }
40
+
41
+ if options[:interactive]
42
+ begin
43
+ require "irb"
44
+ IRB.setup(nil)
45
+ IRB.conf[:IRB_NAME] = "rubydex"
46
+ workspace = IRB::WorkSpace.new(binding)
47
+ IRB::Irb.new(workspace).run(IRB.conf)
48
+ rescue LoadError
49
+ abort("Interactive mode requires `irb` to be in the bundle")
50
+ end
51
+ end
@@ -407,6 +407,28 @@ static VALUE rdxr_variable_declaration_references(VALUE self) {
407
407
  return rb_ary_new();
408
408
  }
409
409
 
410
+ // ConstantAlias#target -> Declaration?
411
+ // Returns the first resolved target declaration for this constant alias, or nil if none of its definitions resolved to
412
+ // a target
413
+ static VALUE rdxr_constant_alias_target(VALUE self) {
414
+ HandleData *data;
415
+ TypedData_Get_Struct(self, HandleData, &handle_type, data);
416
+
417
+ void *graph;
418
+ TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);
419
+
420
+ const CDeclaration *decl = rdx_constant_alias_target(graph, data->id);
421
+ if (decl == NULL) {
422
+ return Qnil;
423
+ }
424
+
425
+ VALUE decl_class = rdxi_declaration_class_for_kind(decl->kind);
426
+ VALUE argv[] = {data->graph_obj, ULL2NUM(decl->id)};
427
+ free_c_declaration(decl);
428
+
429
+ return rb_class_new_instance(2, argv, decl_class);
430
+ }
431
+
410
432
  void rdxi_initialize_declaration(VALUE mRubydex) {
411
433
  cDeclaration = rb_define_class_under(mRubydex, "Declaration", rb_cObject);
412
434
  cNamespace = rb_define_class_under(mRubydex, "Namespace", cDeclaration);
@@ -440,6 +462,7 @@ void rdxi_initialize_declaration(VALUE mRubydex) {
440
462
  // Constant and ConstantAlias have constant references
441
463
  rb_define_method(cConstant, "references", rdxr_constant_declaration_references, 0);
442
464
  rb_define_method(cConstantAlias, "references", rdxr_constant_declaration_references, 0);
465
+ rb_define_method(cConstantAlias, "target", rdxr_constant_alias_target, 0);
443
466
 
444
467
  // Method has method references
445
468
  rb_define_method(cMethod, "references", rdxr_method_declaration_references, 0);
@@ -86,11 +86,15 @@ copy_dylib_commands = if Gem.win_platform?
86
86
  ""
87
87
  elsif RUBY_PLATFORM.include?("darwin")
88
88
  src_dylib = target_dir.join("librubydex_sys.dylib")
89
- "\t$(COPY) #{src_dylib} #{lib_dir}"
89
+ dst_dylib = lib_dir.join("librubydex_sys.dylib")
90
+ # Unlink before copy so the new dylib gets a fresh inode. Overwriting in place while another process
91
+ # (e.g. the Ruby LSP) has the old dylib mmap'd triggers a macOS code-signing SIGKILL on its next page fault.
92
+ "\t$(Q)$(RM) #{dst_dylib}\n\t$(COPY) #{src_dylib} #{lib_dir}"
90
93
  else
91
94
  # Linux
92
95
  src_dylib = target_dir.join("librubydex_sys.so")
93
- "\t$(COPY) #{src_dylib} #{lib_dir}"
96
+ dst_dylib = lib_dir.join("librubydex_sys.so")
97
+ "\t$(Q)$(RM) #{dst_dylib}\n\t$(COPY) #{src_dylib} #{lib_dir}"
94
98
  end
95
99
 
96
100
  rust_srcs = Dir.glob("#{root_dir}/**/*.rs").reject { |path| path.include?("rust/target") }
@@ -137,10 +141,12 @@ end
137
141
 
138
142
  File.write("Makefile", new_makefile)
139
143
 
140
- begin
141
- require "extconf_compile_commands_json"
144
+ if developing_rubydex
145
+ begin
146
+ require "extconf_compile_commands_json"
142
147
 
143
- ExtconfCompileCommandsJson.generate!
144
- ExtconfCompileCommandsJson.symlink!
145
- rescue LoadError # rubocop:disable Lint/SuppressedException
148
+ ExtconfCompileCommandsJson.generate!
149
+ ExtconfCompileCommandsJson.symlink!
150
+ rescue LoadError # rubocop:disable Lint/SuppressedException
151
+ end
146
152
  end
data/ext/rubydex/graph.c CHANGED
@@ -303,6 +303,24 @@ static VALUE rdxr_graph_method_references(VALUE self) {
303
303
  return self;
304
304
  }
305
305
 
306
+ // Graph#document: (String uri) -> Document?
307
+ // Returns the Document for the given URI, or nil if it doesn't exist.
308
+ static VALUE rdxr_graph_document(VALUE self, VALUE uri) {
309
+ Check_Type(uri, T_STRING);
310
+
311
+ void *graph;
312
+ TypedData_Get_Struct(self, void *, &graph_type, graph);
313
+ const uint64_t *uri_id = rdx_graph_get_document(graph, StringValueCStr(uri));
314
+
315
+ if (uri_id == NULL) {
316
+ return Qnil;
317
+ }
318
+
319
+ VALUE argv[] = {self, ULL2NUM(*uri_id)};
320
+ free_u64(uri_id);
321
+ return rb_class_new_instance(2, argv, cDocument);
322
+ }
323
+
306
324
  // Graph#delete_document: (String uri) -> Document?
307
325
  // Deletes a document and all of its definitions from the graph.
308
326
  // Returns the removed Document or nil if it doesn't exist.
@@ -676,6 +694,7 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
676
694
  rb_define_alloc_func(cGraph, rdxr_graph_alloc);
677
695
  rb_define_method(cGraph, "index_all", rdxr_graph_index_all, 1);
678
696
  rb_define_method(cGraph, "index_source", rdxr_graph_index_source, 3);
697
+ rb_define_method(cGraph, "document", rdxr_graph_document, 1);
679
698
  rb_define_method(cGraph, "delete_document", rdxr_graph_delete_document, 1);
680
699
  rb_define_method(cGraph, "resolve", rdxr_graph_resolve, 0);
681
700
  rb_define_method(cGraph, "resolve_constant", rdxr_graph_resolve_constant, 2);
@@ -75,6 +75,28 @@ static VALUE rdxr_method_reference_location(VALUE self) {
75
75
  return location;
76
76
  }
77
77
 
78
+ // MethodReference#receiver -> Rubydex::Declaration?
79
+ // Returns the resolved declaration for the receiver of the method call. Returns nil when the receiver is not a
80
+ // tracked constant or cannot be resolved.
81
+ static VALUE rdxr_method_reference_receiver(VALUE self) {
82
+ HandleData *data;
83
+ TypedData_Get_Struct(self, HandleData, &handle_type, data);
84
+
85
+ void *graph;
86
+ TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);
87
+
88
+ const struct CDeclaration *decl = rdx_method_reference_receiver_declaration(graph, data->id);
89
+ if (decl == NULL) {
90
+ return Qnil;
91
+ }
92
+
93
+ VALUE decl_class = rdxi_declaration_class_for_kind(decl->kind);
94
+ VALUE argv[] = {data->graph_obj, ULL2NUM(decl->id)};
95
+ free_c_declaration(decl);
96
+
97
+ return rb_class_new_instance(2, argv, decl_class);
98
+ }
99
+
78
100
  // ResolvedConstantReference#declaration -> Declaration
79
101
  static VALUE rdxr_resolved_constant_reference_declaration(VALUE self) {
80
102
  HandleData *data;
@@ -120,4 +142,5 @@ void rdxi_initialize_reference(VALUE mRubydex) {
120
142
  rb_define_method(cMethodReference, "initialize", rdxr_handle_initialize, 2);
121
143
  rb_define_method(cMethodReference, "name", rdxr_method_reference_name, 0);
122
144
  rb_define_method(cMethodReference, "location", rdxr_method_reference_location, 0);
145
+ rb_define_method(cMethodReference, "receiver", rdxr_method_reference_receiver, 0);
123
146
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubydex
4
- VERSION = "0.1.0.beta13"
4
+ VERSION = "0.2.0"
5
5
  end
data/rbi/rubydex.rbi CHANGED
@@ -76,6 +76,9 @@ end
76
76
  class Rubydex::ConstantAlias < Rubydex::Declaration
77
77
  sig { returns(T::Enumerable[Rubydex::ConstantReference]) }
78
78
  def references; end
79
+
80
+ sig { returns(T.nilable(Rubydex::Declaration)) }
81
+ def target; end
79
82
  end
80
83
 
81
84
  class Rubydex::ClassVariable < Rubydex::Declaration
@@ -256,6 +259,9 @@ class Rubydex::Graph
256
259
  sig { params(uri: String).returns(T.nilable(Rubydex::Document)) }
257
260
  def delete_document(uri); end
258
261
 
262
+ sig { params(uri: String).returns(T.nilable(Rubydex::Document)) }
263
+ def document(uri); end
264
+
259
265
  sig { returns(T::Array[Rubydex::Diagnostic]) }
260
266
  def diagnostics; end
261
267
 
@@ -412,6 +418,9 @@ class Rubydex::MethodReference < Rubydex::Reference
412
418
 
413
419
  sig { returns(String) }
414
420
  def name; end
421
+
422
+ sig { returns(T.nilable(Rubydex::Declaration)) }
423
+ def receiver; end
415
424
  end
416
425
 
417
426
  class Rubydex::Reference
@@ -107,4 +107,5 @@ rules! {
107
107
  InvalidMethodVisibility;
108
108
 
109
109
  // Resolution
110
+ UndefinedMethodVisibilityTarget;
110
111
  }
@@ -234,6 +234,7 @@ impl<'a> RubyIndexer<'a> {
234
234
  Offset::from_prism_location(&name_loc),
235
235
  str_id,
236
236
  )));
237
+ self.visit(&opt_param.value());
237
238
  }
238
239
 
239
240
  if let Some(rest) = parameters_list.rest() {
@@ -276,6 +277,7 @@ impl<'a> RubyIndexer<'a> {
276
277
  let str_id = self.local_graph.intern_string(self.offset_to_string(&offset));
277
278
 
278
279
  parameters.push(Parameter::OptionalKeyword(ParameterStruct::new(offset, str_id)));
280
+ self.visit(&optional.value());
279
281
  }
280
282
  _ => {}
281
283
  }
@@ -1507,9 +1509,10 @@ impl Visit<'_> for RubyIndexer<'_> {
1507
1509
  };
1508
1510
 
1509
1511
  let string_id = self.local_graph.intern_string(singleton_class_name);
1512
+ let nesting = self.current_lexical_scope_name_id();
1510
1513
  let name_id = self
1511
1514
  .local_graph
1512
- .add_name(Name::new(string_id, ParentScope::Attached(attached_target), None));
1515
+ .add_name(Name::new(string_id, ParentScope::Attached(attached_target), nesting));
1513
1516
 
1514
1517
  let definition = Definition::SingletonClass(Box::new(SingletonClassDefinition::new(
1515
1518
  name_id,
@@ -4060,6 +4060,19 @@ mod constant_reference_tests {
4060
4060
  );
4061
4061
  }
4062
4062
 
4063
+ #[test]
4064
+ fn index_unresolved_constant_references_in_default_values() {
4065
+ let context = index_source({
4066
+ "
4067
+ def foo(a = C1, b = C2::C3); end
4068
+ def bar(a: C4, b: C5::C6); end
4069
+ "
4070
+ });
4071
+
4072
+ assert_no_local_diagnostics!(&context);
4073
+ assert_constant_references_eq!(&context, ["C1", "C2", "C3", "C4", "C5", "C6"]);
4074
+ }
4075
+
4063
4076
  #[test]
4064
4077
  fn index_constant_path_and_write_visits_value() {
4065
4078
  let context = index_source({
@@ -14,6 +14,7 @@ use crate::model::ids::{ConstantReferenceId, DeclarationId, DefinitionId, Method
14
14
  use crate::model::name::{Name, NameRef, ParentScope, ResolvedName};
15
15
  use crate::model::references::{ConstantReference, MethodRef};
16
16
  use crate::model::string_ref::StringRef;
17
+ use crate::model::visibility::Visibility;
17
18
  use crate::stats;
18
19
 
19
20
  /// An entity whose validity depends on a particular `NameId`.
@@ -601,6 +602,44 @@ impl Graph {
601
602
  &self.name_dependents
602
603
  }
603
604
 
605
+ /// Returns the visibility for a method declaration.
606
+ ///
607
+ /// Scans the declaration's backing definitions from the end:
608
+ /// - First `MethodVisibilityDefinition` wins
609
+ /// - Otherwise, falls back to the latest method-like definition's indexed visibility
610
+ /// - Returns `None` if the declaration has no definitions with visibility
611
+ #[must_use]
612
+ pub fn visibility(&self, declaration_id: &DeclarationId) -> Option<Visibility> {
613
+ let declaration = self.declarations.get(declaration_id)?;
614
+ let definitions = declaration.definitions();
615
+
616
+ // Scan from the end: last visibility directive wins
617
+ for def_id in definitions.iter().rev() {
618
+ if let Some(definition) = self.definitions.get(def_id) {
619
+ match definition {
620
+ Definition::MethodVisibility(vis) => return Some(*vis.visibility()),
621
+ Definition::Method(method) => return Some(*method.visibility()),
622
+ Definition::AttrAccessor(attr) => return Some(*attr.visibility()),
623
+ Definition::AttrReader(attr) => return Some(*attr.visibility()),
624
+ Definition::AttrWriter(attr) => return Some(*attr.visibility()),
625
+ Definition::Class(_)
626
+ | Definition::SingletonClass(_)
627
+ | Definition::Module(_)
628
+ | Definition::Constant(_)
629
+ | Definition::ConstantAlias(_)
630
+ | Definition::ConstantVisibility(_)
631
+ | Definition::GlobalVariable(_)
632
+ | Definition::InstanceVariable(_)
633
+ | Definition::ClassVariable(_)
634
+ | Definition::MethodAlias(_)
635
+ | Definition::GlobalVariableAlias(_) => {}
636
+ }
637
+ }
638
+ }
639
+
640
+ None
641
+ }
642
+
604
643
  /// Drains the accumulated work items, returning them for use by the resolver.
605
644
  pub fn take_pending_work(&mut self) -> Vec<Unit> {
606
645
  std::mem::take(&mut self.pending_work)
@@ -2234,14 +2273,14 @@ mod tests {
2234
2273
  assert_eq!(13, context.graph().constant_references.len());
2235
2274
  assert_eq!(2, context.graph().method_references.len());
2236
2275
  assert_eq!(2, context.graph().documents.len());
2237
- assert_eq!(19, context.graph().names.len());
2276
+ assert_eq!(20, context.graph().names.len());
2238
2277
  assert_eq!(47, context.graph().strings.len());
2239
2278
  context.index_uri("file:///foo.rb", source);
2240
2279
  assert_eq!(49, context.graph().definitions.len());
2241
2280
  assert_eq!(13, context.graph().constant_references.len());
2242
2281
  assert_eq!(2, context.graph().method_references.len());
2243
2282
  assert_eq!(2, context.graph().documents.len());
2244
- assert_eq!(19, context.graph().names.len());
2283
+ assert_eq!(20, context.graph().names.len());
2245
2284
  assert_eq!(47, context.graph().strings.len());
2246
2285
  }
2247
2286
 
@@ -897,7 +897,7 @@ mod tests {
897
897
  context.resolve();
898
898
 
899
899
  let foo_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
900
- let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), None).id();
900
+ let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), Some(foo_id)).id();
901
901
  assert_declaration_completion_eq!(
902
902
  context,
903
903
  CompletionReceiver::Expression(name_id),
@@ -3,6 +3,7 @@ use std::{
3
3
  hash::BuildHasher,
4
4
  };
5
5
 
6
+ use crate::diagnostic::{Diagnostic, Rule};
6
7
  use crate::model::{
7
8
  built_in::{BASIC_OBJECT_ID, CLASS_ID, KERNEL_ID, MODULE_ID, OBJECT_ID},
8
9
  declaration::{
@@ -272,6 +273,8 @@ impl<'a> Resolver<'a> {
272
273
  /// Handle other definitions that don't require resolution, but need to have their declarations and membership created
273
274
  #[allow(clippy::too_many_lines)]
274
275
  fn handle_remaining_definitions(&mut self, other_ids: Vec<DefinitionId>) {
276
+ let mut method_visibility_ids = Vec::new();
277
+
275
278
  for id in other_ids {
276
279
  match self.graph.definitions().get(&id).unwrap() {
277
280
  Definition::Method(method_definition) => {
@@ -538,8 +541,8 @@ impl<'a> Resolver<'a> {
538
541
  Definition::ConstantVisibility(_constant_visibility) => {
539
542
  // TODO
540
543
  }
541
- Definition::MethodVisibility(_method_visibility) => {
542
- // TODO
544
+ Definition::MethodVisibility(_) => {
545
+ method_visibility_ids.push(id);
543
546
  }
544
547
  Definition::Class(_)
545
548
  | Definition::SingletonClass(_)
@@ -550,6 +553,91 @@ impl<'a> Resolver<'a> {
550
553
  }
551
554
  }
552
555
  }
556
+
557
+ self.resolve_method_visibilities(method_visibility_ids);
558
+ }
559
+
560
+ /// Resolves retroactive method visibility changes (`private :foo`, `protected :foo`, `public :foo`).
561
+ ///
562
+ /// Runs as a second pass after all methods/attrs are declared, so `private :bar` works
563
+ /// regardless of whether `def bar` appeared before or after it in source.
564
+ fn resolve_method_visibilities(&mut self, visibility_ids: Vec<DefinitionId>) {
565
+ let mut pending_work = Vec::new();
566
+
567
+ for id in visibility_ids {
568
+ let Definition::MethodVisibility(method_visibility) = self.graph.definitions().get(&id).unwrap() else {
569
+ unreachable!()
570
+ };
571
+
572
+ let str_id = *method_visibility.str_id();
573
+ let uri_id = *method_visibility.uri_id();
574
+ let offset = method_visibility.offset().clone();
575
+ let lexical_nesting_id = *method_visibility.lexical_nesting_id();
576
+
577
+ let Some(owner_id) = self.resolve_lexical_owner(lexical_nesting_id) else {
578
+ continue;
579
+ };
580
+
581
+ let Some(Declaration::Namespace(namespace)) = self.graph.declarations().get(&owner_id) else {
582
+ continue;
583
+ };
584
+
585
+ let mut visibility_applied = false;
586
+ let mut has_partial = false;
587
+
588
+ for ancestor in namespace.ancestors() {
589
+ match ancestor {
590
+ Ancestor::Complete(ancestor_id) => {
591
+ let has_member = self
592
+ .graph
593
+ .declarations()
594
+ .get(ancestor_id)
595
+ .and_then(|decl| decl.as_namespace())
596
+ .and_then(|ns| ns.member(&str_id))
597
+ .is_some();
598
+
599
+ if has_member {
600
+ // Direct member: `create_declaration`'s fully qualified name dedup attaches
601
+ // this visibility definition to the existing method declaration.
602
+ // Inherited: a new child-owned declaration is created.
603
+ self.create_declaration(str_id, id, owner_id, |name| {
604
+ Declaration::Method(Box::new(MethodDeclaration::new(name, owner_id)))
605
+ });
606
+ visibility_applied = true;
607
+ break;
608
+ }
609
+ }
610
+ Ancestor::Partial(_) => has_partial = true,
611
+ }
612
+ }
613
+
614
+ if visibility_applied {
615
+ continue;
616
+ }
617
+
618
+ if has_partial {
619
+ // Method might exist on an unresolved ancestor — requeue for retry.
620
+ pending_work.push(Unit::Definition(id));
621
+ } else {
622
+ // Ancestors are fully resolved — method definitively doesn't exist.
623
+ let method_name = self.graph.strings().get(&str_id).unwrap().as_str().to_string();
624
+ let owner_name = self.graph.declarations().get(&owner_id).unwrap().name().to_string();
625
+ let diagnostic = Diagnostic::new(
626
+ Rule::UndefinedMethodVisibilityTarget,
627
+ uri_id,
628
+ offset,
629
+ format!("undefined method `{method_name}` for visibility change in `{owner_name}`"),
630
+ );
631
+ self.graph
632
+ .declarations_mut()
633
+ .get_mut(&owner_id)
634
+ .unwrap()
635
+ .add_diagnostic(diagnostic);
636
+ }
637
+ }
638
+
639
+ // Must extend work here so incremental resolution can resolve previously unresolved visibility operations
640
+ self.graph.extend_work(pending_work);
553
641
  }
554
642
 
555
643
  fn create_declaration<F>(