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 +4 -4
- data/exe/rdx +28 -24
- data/ext/rubydex/declaration.c +23 -0
- data/ext/rubydex/extconf.rb +13 -7
- data/ext/rubydex/graph.c +19 -0
- data/ext/rubydex/reference.c +23 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +9 -0
- data/rust/rubydex/src/diagnostic.rs +1 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +4 -1
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +13 -0
- data/rust/rubydex/src/model/graph.rs +41 -2
- data/rust/rubydex/src/query.rs +1 -1
- data/rust/rubydex/src/resolution.rs +90 -2
- data/rust/rubydex/src/resolution_tests.rs +4627 -4197
- data/rust/rubydex-sys/src/declaration_api.rs +28 -0
- data/rust/rubydex-sys/src/graph_api.rs +24 -1
- data/rust/rubydex-sys/src/name_api.rs +9 -7
- data/rust/rubydex-sys/src/reference_api.rs +37 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62ff27bd44d2fb24baaea47fbead5e90a99b129d77fb189e9410ba7a04b6a1df
|
|
4
|
+
data.tar.gz: 9acd6049e9e44db280b8a6665b7ac6772703d28e89c3b016b8ccedd133fc1de6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
data/ext/rubydex/declaration.c
CHANGED
|
@@ -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);
|
data/ext/rubydex/extconf.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
144
|
+
if developing_rubydex
|
|
145
|
+
begin
|
|
146
|
+
require "extconf_compile_commands_json"
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
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);
|
data/ext/rubydex/reference.c
CHANGED
|
@@ -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
|
}
|
data/lib/rubydex/version.rb
CHANGED
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
|
|
@@ -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),
|
|
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!(
|
|
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!(
|
|
2283
|
+
assert_eq!(20, context.graph().names.len());
|
|
2245
2284
|
assert_eq!(47, context.graph().strings.len());
|
|
2246
2285
|
}
|
|
2247
2286
|
|
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -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),
|
|
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(
|
|
542
|
-
|
|
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>(
|