Skip to content

Commit a421b3c

Browse files
committed
Add without_resolution option to skip accumulating pending work
1 parent 65847d6 commit a421b3c

File tree

5 files changed

+110
-4
lines changed

5 files changed

+110
-4
lines changed

ext/rubydex/graph.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,24 @@ static VALUE rdxr_graph_set_encoding(VALUE self, VALUE encoding) {
356356
return Qnil;
357357
}
358358

359+
// Graph#without_resolution=: (Boolean) -> void
360+
// Configures the graph to skip accumulating resolution work items
361+
static VALUE rdxr_graph_set_without_resolution(VALUE self, VALUE without_resolution) {
362+
void *graph;
363+
TypedData_Get_Struct(self, void *, &graph_type, graph);
364+
rdx_graph_set_without_resolution(graph, RTEST(without_resolution));
365+
return Qnil;
366+
}
367+
368+
// Graph#without_resolution?: () -> Boolean
369+
// Returns whether the graph is configured to skip accumulating resolution work items
370+
static VALUE rdxr_graph_without_resolution(VALUE self) {
371+
void *graph;
372+
TypedData_Get_Struct(self, void *, &graph_type, graph);
373+
374+
return rdx_graph_without_resolution(graph) ? Qtrue : Qfalse;
375+
}
376+
359377
// Graph#resolve_constant: (String, Array[String]) -> Declaration?
360378
// Runs the resolver on a single constant reference to determine what it points to
361379
static VALUE rdxr_graph_resolve_constant(VALUE self, VALUE const_name, VALUE nesting) {
@@ -498,6 +516,8 @@ void rdxi_initialize_graph(VALUE mRubydex) {
498516
rb_define_method(cGraph, "[]", rdxr_graph_aref, 1);
499517
rb_define_method(cGraph, "search", rdxr_graph_search, 1);
500518
rb_define_method(cGraph, "encoding=", rdxr_graph_set_encoding, 1);
519+
rb_define_method(cGraph, "without_resolution=", rdxr_graph_set_without_resolution, 1);
520+
rb_define_method(cGraph, "without_resolution?", rdxr_graph_without_resolution, 0);
501521
rb_define_method(cGraph, "resolve_require_path", rdxr_graph_resolve_require_path, 2);
502522
rb_define_method(cGraph, "require_paths", rdxr_graph_require_paths, 1);
503523
}

lib/rubydex/graph.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ class Graph
1616
#: String
1717
attr_accessor :workspace_path
1818

19-
#: (?workspace_path: String) -> void
20-
def initialize(workspace_path: Dir.pwd)
19+
#: (?workspace_path: String, ?without_resolution: bool) -> void
20+
def initialize(workspace_path: Dir.pwd, without_resolution: false)
2121
@workspace_path = workspace_path
22+
self.without_resolution = without_resolution
2223
end
2324

2425
# Index all files and dependencies of the workspace that exists in `@workspace_path`

rust/rubydex-sys/src/graph_api.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,28 @@ pub unsafe extern "C" fn rdx_graph_set_encoding(pointer: GraphPointer, encoding_
221221
true
222222
}
223223

224+
/// Configures the graph to skip accumulating resolution work items.
225+
///
226+
/// # Safety
227+
///
228+
/// Expects the graph pointer to be valid
229+
#[unsafe(no_mangle)]
230+
pub unsafe extern "C" fn rdx_graph_set_without_resolution(pointer: GraphPointer, without_resolution: bool) {
231+
with_mut_graph(pointer, |graph| {
232+
graph.set_without_resolution(without_resolution);
233+
});
234+
}
235+
236+
/// Returns whether the graph is configured to skip accumulating resolution work items.
237+
///
238+
/// # Safety
239+
///
240+
/// Expects the graph pointer to be valid
241+
#[unsafe(no_mangle)]
242+
pub unsafe extern "C" fn rdx_graph_without_resolution(pointer: GraphPointer) -> bool {
243+
with_graph(pointer, |graph| graph.without_resolution())
244+
}
245+
224246
/// Creates a new iterator over declaration IDs by snapshotting the current set of IDs.
225247
///
226248
/// # Safety

rust/rubydex/src/model/graph.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ pub struct Graph {
7777
/// Used during invalidation to efficiently find affected entities without scanning the full graph.
7878
name_dependents: IdentityHashMap<NameId, Vec<NameDependent>>,
7979

80+
/// When true, work items from update/delete are discarded instead of accumulated.
81+
/// Use for tools that only need definitions without resolution.
82+
without_resolution: bool,
83+
8084
/// Accumulated work items from update/delete operations.
8185
/// Drained by `take_pending_work()` before resolution.
8286
pending_work: Vec<Unit>,
@@ -96,6 +100,7 @@ impl Graph {
96100
position_encoding: Encoding::default(),
97101
declaration_to_names: IdentityHashMap::default(),
98102
name_dependents: IdentityHashMap::default(),
103+
without_resolution: false,
99104
pending_work: Vec::default(),
100105
}
101106
}
@@ -718,7 +723,9 @@ impl Graph {
718723
let mut work = Vec::new();
719724
self.invalidate(Some(&document), None, &mut work);
720725
self.remove_document_data(&document);
721-
self.pending_work.extend(work);
726+
if !self.without_resolution {
727+
self.pending_work.extend(work);
728+
}
722729
Some(uri_id)
723730
}
724731

@@ -811,7 +818,9 @@ impl Graph {
811818
}
812819

813820
self.extend(other, &mut work);
814-
self.pending_work.extend(work);
821+
if !self.without_resolution {
822+
self.pending_work.extend(work);
823+
}
815824
}
816825

817826
/// Detaches old definitions from declarations, identifies declarations touched by the
@@ -1140,6 +1149,17 @@ impl Graph {
11401149
false
11411150
}
11421151

1152+
/// Configures the graph to skip accumulating resolution work items. Use for tools that only
1153+
/// need definitions and don't intend to call `resolve()`.
1154+
pub fn set_without_resolution(&mut self, without_resolution: bool) {
1155+
self.without_resolution = without_resolution;
1156+
}
1157+
1158+
#[must_use]
1159+
pub fn without_resolution(&self) -> bool {
1160+
self.without_resolution
1161+
}
1162+
11431163
/// Sets the encoding that should be used for transforming byte offsets into LSP code unit line/column positions
11441164
pub fn set_encoding(&mut self, encoding: Encoding) {
11451165
self.position_encoding = encoding;

test/graph_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,49 @@ def test_workspace_paths
562562

563563
private
564564

565+
def test_without_resolution_prevents_memory_growth
566+
with_context do |context|
567+
context.write!("foo.rb", <<~RUBY)
568+
module Foo
569+
CONST = 1
570+
end
571+
RUBY
572+
573+
context.write!("bar.rb", <<~RUBY)
574+
class Bar
575+
Foo::CONST
576+
end
577+
RUBY
578+
579+
graph = Rubydex::Graph.new(without_resolution: true)
580+
assert(graph.without_resolution?)
581+
graph.index_all(context.glob("**/*.rb"))
582+
583+
# Without resolution, declarations should still be created from definitions
584+
refute_nil(graph["Foo"])
585+
refute_nil(graph["Bar"])
586+
587+
# Repeated re-indexing should not accumulate unbounded work
588+
10.times do
589+
graph.index_source(context.uri_to("bar.rb"), context.absolute_path_to("bar.rb"), <<~RUBY)
590+
class Bar
591+
Foo::CONST
592+
end
593+
RUBY
594+
end
595+
596+
# Graph still works for definition lookups
597+
refute_nil(graph["Foo"])
598+
refute_nil(graph["Bar"])
599+
end
600+
end
601+
602+
def test_without_resolution_defaults_to_false
603+
graph = Rubydex::Graph.new
604+
refute(graph.without_resolution?)
605+
end
606+
607+
565608
def assert_diagnostics(expected, actual)
566609
assert_equal(
567610
expected,

0 commit comments

Comments
 (0)