rubydex 0.2.7 → 0.2.8
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/graph.c +28 -14
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +4 -4
- data/rust/rubydex/src/main.rs +148 -5
- data/rust/rubydex/src/model/declaration.rs +14 -14
- data/rust/rubydex/src/model/graph.rs +85 -2
- data/rust/rubydex/src/model/ids.rs +30 -0
- data/rust/rubydex/src/model/string_ref.rs +12 -4
- data/rust/rubydex/src/query.rs +83 -15
- data/rust/rubydex/src/resolution.rs +196 -139
- data/rust/rubydex/tests/cli.rs +66 -0
- data/rust/rubydex-mcp/src/server.rs +1 -1
- data/rust/rubydex-sys/src/declaration_api.rs +2 -2
- data/rust/rubydex-sys/src/graph_api.rs +54 -43
- 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: 6b27fe66d59bdafd3b040278baaca0607a87861d132d70cc7ad65f9edb75c049
|
|
4
|
+
data.tar.gz: 1036e909678edd8183e1359ba9b45323d1e7a26ad99847d3382583a3f97d1f61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 884a896d38505cdb97ddc2a83b96364dc918f7f449349bf37e0d54675d630750300856df10dc6ab13e80b20d03979cbef5122ca2cc7ed20a160e64cf8ec421bd
|
|
7
|
+
data.tar.gz: cfd66ff47db6ac89e6f5514d307e48dcb5f252c5132eeb17328cf067365c1d764b6f9f87609483135815bf5760502992a9de107322e9445b2a99deacc9dcf7a5
|
data/ext/rubydex/graph.c
CHANGED
|
@@ -196,40 +196,54 @@ static VALUE rdxr_graph_yield_search_results(VALUE self, void *iter) {
|
|
|
196
196
|
|
|
197
197
|
/*
|
|
198
198
|
* call-seq:
|
|
199
|
-
* search(
|
|
199
|
+
* search(*queries) -> Enumerator[Rubydex::Declaration]
|
|
200
200
|
*
|
|
201
|
-
* Returns an enumerator that yields declarations
|
|
201
|
+
* Returns an enumerator that yields declarations whose name matches any of the queries exactly by substring.
|
|
202
202
|
*/
|
|
203
|
-
static VALUE rdxr_graph_search(VALUE
|
|
204
|
-
|
|
203
|
+
static VALUE rdxr_graph_search(int argc, VALUE *argv, VALUE self) {
|
|
204
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
205
|
+
VALUE queries = rb_ary_new_from_values(argc, argv);
|
|
206
|
+
rdxi_check_array_of_strings(queries);
|
|
205
207
|
|
|
206
208
|
if (!rb_block_given_p()) {
|
|
207
|
-
return rb_enumeratorize(self, rb_str_new2("search"),
|
|
209
|
+
return rb_enumeratorize(self, rb_str_new2("search"), argc, argv);
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
void *graph;
|
|
211
213
|
TypedData_Get_Struct(self, void *, &graph_type, graph);
|
|
212
214
|
|
|
213
|
-
|
|
215
|
+
size_t length = (size_t)argc;
|
|
216
|
+
char **converted = rdxi_str_array_to_char(queries, length);
|
|
217
|
+
void *iter = rdx_graph_declarations_search(graph, (const char *const *)converted, length);
|
|
218
|
+
rdxi_free_str_array(converted, length);
|
|
219
|
+
|
|
220
|
+
return rdxr_graph_yield_search_results(self, iter);
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
/*
|
|
217
224
|
* call-seq:
|
|
218
|
-
* fuzzy_search(
|
|
225
|
+
* fuzzy_search(*queries) -> Enumerator[Rubydex::Declaration]
|
|
219
226
|
*
|
|
220
|
-
* Returns an enumerator that yields declarations
|
|
227
|
+
* Returns an enumerator that yields declarations whose name matches any of the queries fuzzily.
|
|
221
228
|
*/
|
|
222
|
-
static VALUE rdxr_graph_fuzzy_search(VALUE
|
|
223
|
-
|
|
229
|
+
static VALUE rdxr_graph_fuzzy_search(int argc, VALUE *argv, VALUE self) {
|
|
230
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
231
|
+
VALUE queries = rb_ary_new_from_values(argc, argv);
|
|
232
|
+
rdxi_check_array_of_strings(queries);
|
|
224
233
|
|
|
225
234
|
if (!rb_block_given_p()) {
|
|
226
|
-
return rb_enumeratorize(self, rb_str_new2("fuzzy_search"),
|
|
235
|
+
return rb_enumeratorize(self, rb_str_new2("fuzzy_search"), argc, argv);
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
void *graph;
|
|
230
239
|
TypedData_Get_Struct(self, void *, &graph_type, graph);
|
|
231
240
|
|
|
232
|
-
|
|
241
|
+
size_t length = (size_t)argc;
|
|
242
|
+
char **converted = rdxi_str_array_to_char(queries, length);
|
|
243
|
+
void *iter = rdx_graph_declarations_fuzzy_search(graph, (const char *const *)converted, length);
|
|
244
|
+
rdxi_free_str_array(converted, length);
|
|
245
|
+
|
|
246
|
+
return rdxr_graph_yield_search_results(self, iter);
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
// Body function for rb_ensure in Graph#documents
|
|
@@ -942,8 +956,8 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
|
|
|
942
956
|
rb_define_method(cGraph, "diagnostics", rdxr_graph_diagnostics, 0);
|
|
943
957
|
rb_define_method(cGraph, "check_integrity", rdxr_graph_check_integrity, 0);
|
|
944
958
|
rb_define_method(cGraph, "[]", rdxr_graph_aref, 1);
|
|
945
|
-
rb_define_method(cGraph, "search", rdxr_graph_search, 1);
|
|
946
|
-
rb_define_method(cGraph, "fuzzy_search", rdxr_graph_fuzzy_search, 1);
|
|
959
|
+
rb_define_method(cGraph, "search", rdxr_graph_search, -1);
|
|
960
|
+
rb_define_method(cGraph, "fuzzy_search", rdxr_graph_fuzzy_search, -1);
|
|
947
961
|
rb_define_method(cGraph, "encoding=", rdxr_graph_set_encoding, 1);
|
|
948
962
|
rb_define_method(cGraph, "resolve_require_path", rdxr_graph_resolve_require_path, 2);
|
|
949
963
|
rb_define_method(cGraph, "require_paths", rdxr_graph_require_paths, 1);
|
|
Binary file
|
data/lib/rubydex/version.rb
CHANGED
data/rbi/rubydex.rbi
CHANGED
|
@@ -335,11 +335,11 @@ class Rubydex::Graph
|
|
|
335
335
|
sig { params(require_path: String, load_paths: T::Array[String]).returns(T.nilable(Rubydex::Document)) }
|
|
336
336
|
def resolve_require_path(require_path, load_paths); end
|
|
337
337
|
|
|
338
|
-
sig { params(
|
|
339
|
-
def search(
|
|
338
|
+
sig { params(queries: String).returns(T::Enumerable[Rubydex::Declaration]) }
|
|
339
|
+
def search(*queries); end
|
|
340
340
|
|
|
341
|
-
sig { params(
|
|
342
|
-
def fuzzy_search(
|
|
341
|
+
sig { params(queries: String).returns(T::Enumerable[Rubydex::Declaration]) }
|
|
342
|
+
def fuzzy_search(*queries); end
|
|
343
343
|
|
|
344
344
|
sig { params(encoding: String).void }
|
|
345
345
|
def encoding=(encoding); end
|
data/rust/rubydex/src/main.rs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
use clap::{Parser, ValueEnum};
|
|
2
|
-
use std::{
|
|
2
|
+
use std::{
|
|
3
|
+
fs, mem,
|
|
4
|
+
path::{Path, PathBuf},
|
|
5
|
+
time::{Duration, Instant},
|
|
6
|
+
};
|
|
3
7
|
|
|
4
8
|
use rubydex::{
|
|
5
9
|
dot,
|
|
6
|
-
indexing::{self, IndexerBackend},
|
|
10
|
+
indexing::{self, IndexerBackend, LanguageId, build_local_graph},
|
|
7
11
|
integrity, listing,
|
|
8
12
|
model::graph::Graph,
|
|
9
13
|
resolution::Resolver,
|
|
@@ -12,12 +16,17 @@ use rubydex::{
|
|
|
12
16
|
timer::{Timer, time_it},
|
|
13
17
|
},
|
|
14
18
|
};
|
|
19
|
+
use url::Url;
|
|
15
20
|
|
|
16
21
|
#[derive(Parser, Debug)]
|
|
17
22
|
#[command(name = "rubydex_cli", about = "A Static Analysis Toolkit for Ruby", version)]
|
|
18
23
|
#[allow(clippy::struct_excessive_bools)]
|
|
19
24
|
struct Args {
|
|
20
|
-
#[arg(
|
|
25
|
+
#[arg(
|
|
26
|
+
value_name = "PATHS",
|
|
27
|
+
default_value = ".",
|
|
28
|
+
help = "Path(s) to index. If the first path is a directory, it is used as the workspace root for rubydex.toml"
|
|
29
|
+
)]
|
|
21
30
|
paths: Vec<String>,
|
|
22
31
|
|
|
23
32
|
#[arg(long = "stop-after", help = "Stop after the given stage")]
|
|
@@ -52,6 +61,21 @@ struct Args {
|
|
|
52
61
|
help = "Write orphan definitions report to specified file"
|
|
53
62
|
)]
|
|
54
63
|
report_orphans: Option<String>,
|
|
64
|
+
|
|
65
|
+
#[arg(
|
|
66
|
+
long = "incremental_cycle",
|
|
67
|
+
value_name = "N",
|
|
68
|
+
help = "After the initial build, run N incremental resolution cycles and report their timings"
|
|
69
|
+
)]
|
|
70
|
+
incremental_cycle: Option<usize>,
|
|
71
|
+
|
|
72
|
+
#[arg(
|
|
73
|
+
long = "incremental_files",
|
|
74
|
+
value_name = "N",
|
|
75
|
+
default_value_t = 1,
|
|
76
|
+
help = "Number of files to re-index per incremental cycle"
|
|
77
|
+
)]
|
|
78
|
+
incremental_files: usize,
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
#[derive(Debug, Clone, ValueEnum)]
|
|
@@ -85,6 +109,11 @@ fn exit(print_stats: bool) {
|
|
|
85
109
|
std::process::exit(0);
|
|
86
110
|
}
|
|
87
111
|
|
|
112
|
+
fn workspace_path_for(paths: &[String]) -> Option<PathBuf> {
|
|
113
|
+
let first_path = paths.first()?;
|
|
114
|
+
fs::canonicalize(first_path).ok().filter(|path| path.is_dir())
|
|
115
|
+
}
|
|
116
|
+
|
|
88
117
|
fn main() {
|
|
89
118
|
let args = Args::parse();
|
|
90
119
|
|
|
@@ -92,9 +121,21 @@ fn main() {
|
|
|
92
121
|
Timer::set_global_timer(Timer::new());
|
|
93
122
|
}
|
|
94
123
|
|
|
124
|
+
let mut graph = Graph::new();
|
|
125
|
+
|
|
126
|
+
if let Some(workspace_path) = workspace_path_for(&args.paths) {
|
|
127
|
+
graph.set_workspace_path(workspace_path);
|
|
128
|
+
if let Err(error) = graph.load_config(None) {
|
|
129
|
+
eprintln!("{error}");
|
|
130
|
+
std::process::exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
// Listing
|
|
96
135
|
|
|
97
|
-
let (file_paths, errors) = time_it!(listing, {
|
|
136
|
+
let (file_paths, errors) = time_it!(listing, {
|
|
137
|
+
listing::collect_file_paths(args.paths, &graph.excluded_paths())
|
|
138
|
+
});
|
|
98
139
|
|
|
99
140
|
for error in errors {
|
|
100
141
|
eprintln!("{error}");
|
|
@@ -106,8 +147,16 @@ fn main() {
|
|
|
106
147
|
|
|
107
148
|
// Indexing
|
|
108
149
|
|
|
109
|
-
let mut graph = Graph::new();
|
|
110
150
|
let backend = IndexerBackend::from(&args.indexer);
|
|
151
|
+
|
|
152
|
+
// The incremental benchmark re-indexes files after the initial build, so keep a copy of the
|
|
153
|
+
// paths before `index_files` consumes them.
|
|
154
|
+
let incremental_paths = if args.incremental_cycle.is_some() {
|
|
155
|
+
file_paths.clone()
|
|
156
|
+
} else {
|
|
157
|
+
Vec::new()
|
|
158
|
+
};
|
|
159
|
+
|
|
111
160
|
let errors = time_it!(indexing, { indexing::index_files(&mut graph, file_paths, backend) });
|
|
112
161
|
|
|
113
162
|
for error in errors {
|
|
@@ -125,6 +174,12 @@ fn main() {
|
|
|
125
174
|
resolver.resolve();
|
|
126
175
|
});
|
|
127
176
|
|
|
177
|
+
// Incremental resolution benchmark. Runs before the stop-after check so it can be combined with
|
|
178
|
+
// `--stop-after=resolution`.
|
|
179
|
+
if let Some(cycles) = args.incremental_cycle {
|
|
180
|
+
run_incremental_resolution(&mut graph, &incremental_paths, cycles, args.incremental_files, backend);
|
|
181
|
+
}
|
|
182
|
+
|
|
128
183
|
if let Some(StopAfter::Resolution) = args.stop_after {
|
|
129
184
|
return exit(args.stats);
|
|
130
185
|
}
|
|
@@ -186,3 +241,91 @@ fn main() {
|
|
|
186
241
|
// Forget the graph so we don't have to wait for deallocation and let the system reclaim the memory at exit
|
|
187
242
|
mem::forget(graph);
|
|
188
243
|
}
|
|
244
|
+
|
|
245
|
+
/// Simulates incremental editing to measure incremental resolution cost. For each cycle it
|
|
246
|
+
/// re-indexes a rotating window of `files_per_cycle` files (parsing plus the same invalidation the
|
|
247
|
+
/// LSP performs on save) and then re-runs resolution over the resulting pending work, reporting
|
|
248
|
+
/// per-cycle and aggregate `resolve()` timings.
|
|
249
|
+
///
|
|
250
|
+
/// With `--stats`, the `compute_descendants` breakdown printed in the timing summary reflects the
|
|
251
|
+
/// last incremental cycle, since it is recorded on every `resolve()`.
|
|
252
|
+
fn run_incremental_resolution(
|
|
253
|
+
graph: &mut Graph,
|
|
254
|
+
paths: &[PathBuf],
|
|
255
|
+
cycles: usize,
|
|
256
|
+
files_per_cycle: usize,
|
|
257
|
+
backend: IndexerBackend,
|
|
258
|
+
) {
|
|
259
|
+
if paths.is_empty() || cycles == 0 || files_per_cycle == 0 {
|
|
260
|
+
eprintln!("Skipping incremental resolution: nothing to re-index");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let files_per_cycle = files_per_cycle.min(paths.len());
|
|
265
|
+
|
|
266
|
+
let mut reindex_total = Duration::ZERO;
|
|
267
|
+
let mut resolve_total = Duration::ZERO;
|
|
268
|
+
let mut resolve_min = Duration::MAX;
|
|
269
|
+
let mut resolve_max = Duration::ZERO;
|
|
270
|
+
|
|
271
|
+
println!();
|
|
272
|
+
println!("Incremental resolution ({cycles} cycle(s), {files_per_cycle} file(s)/cycle)");
|
|
273
|
+
println!(" Scenario: no-op reindex; files are indexed again without changing their contents.");
|
|
274
|
+
println!(" This should be the fastest incremental resolution path.");
|
|
275
|
+
println!(" Add scenario-based benchmarks before optimizing incremental resolution.");
|
|
276
|
+
|
|
277
|
+
for cycle in 0..cycles {
|
|
278
|
+
// Re-index the window of files for this cycle. Rotating the window across cycles samples
|
|
279
|
+
// different parts of the codebase rather than measuring the same delta repeatedly.
|
|
280
|
+
let reindex_start = Instant::now();
|
|
281
|
+
for i in 0..files_per_cycle {
|
|
282
|
+
let path = &paths[(cycle * files_per_cycle + i) % paths.len()];
|
|
283
|
+
reindex_file(graph, path, backend);
|
|
284
|
+
}
|
|
285
|
+
reindex_total += reindex_start.elapsed();
|
|
286
|
+
|
|
287
|
+
let resolve_start = Instant::now();
|
|
288
|
+
Resolver::new(graph).resolve();
|
|
289
|
+
let elapsed = resolve_start.elapsed();
|
|
290
|
+
|
|
291
|
+
resolve_total += elapsed;
|
|
292
|
+
resolve_min = resolve_min.min(elapsed);
|
|
293
|
+
resolve_max = resolve_max.max(elapsed);
|
|
294
|
+
|
|
295
|
+
println!(
|
|
296
|
+
" cycle {:>3}: resolve {:9.3}ms",
|
|
297
|
+
cycle + 1,
|
|
298
|
+
elapsed.as_secs_f64() * 1000.0
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let avg = resolve_total / u32::try_from(cycles).expect("cycle count fits in u32");
|
|
303
|
+
|
|
304
|
+
println!(
|
|
305
|
+
" resolve total {:.3}ms avg {:.3}ms min {:.3}ms max {:.3}ms",
|
|
306
|
+
resolve_total.as_secs_f64() * 1000.0,
|
|
307
|
+
avg.as_secs_f64() * 1000.0,
|
|
308
|
+
resolve_min.as_secs_f64() * 1000.0,
|
|
309
|
+
resolve_max.as_secs_f64() * 1000.0,
|
|
310
|
+
);
|
|
311
|
+
println!(
|
|
312
|
+
" reindex+invalidate total {:.3}ms (parse + document merge, excluded from resolve above)",
|
|
313
|
+
reindex_total.as_secs_f64() * 1000.0,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Re-indexes a single file into the graph, running the same invalidation the LSP performs on save.
|
|
318
|
+
fn reindex_file(graph: &mut Graph, path: &Path, backend: IndexerBackend) {
|
|
319
|
+
let Ok(source) = fs::read_to_string(path) else {
|
|
320
|
+
eprintln!("Failed to read file `{}`", path.display());
|
|
321
|
+
return;
|
|
322
|
+
};
|
|
323
|
+
let Ok(url) = Url::from_file_path(path) else {
|
|
324
|
+
eprintln!("Couldn't build URI from path `{}`", path.display());
|
|
325
|
+
return;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
let language = path.extension().map_or(LanguageId::Ruby, LanguageId::from);
|
|
329
|
+
let local_graph = build_local_graph(url.to_string(), &source, &language, backend);
|
|
330
|
+
graph.consume_document_changes(local_graph);
|
|
331
|
+
}
|
|
@@ -91,7 +91,7 @@ macro_rules! namespace_declaration {
|
|
|
91
91
|
#[derive(Debug)]
|
|
92
92
|
pub struct $name {
|
|
93
93
|
/// The fully qualified name of this declaration
|
|
94
|
-
name:
|
|
94
|
+
name: Box<str>,
|
|
95
95
|
/// The list of definition IDs that compose this declaration
|
|
96
96
|
definition_ids: Vec<DefinitionId>,
|
|
97
97
|
/// The set of references that are made to this declaration
|
|
@@ -116,7 +116,7 @@ macro_rules! namespace_declaration {
|
|
|
116
116
|
#[must_use]
|
|
117
117
|
pub fn new(name: String, owner_id: DeclarationId) -> Self {
|
|
118
118
|
Self {
|
|
119
|
-
name,
|
|
119
|
+
name: name.into_boxed_str(),
|
|
120
120
|
definition_ids: Vec::new(),
|
|
121
121
|
members: IdentityHashMap::default(),
|
|
122
122
|
references: IdentityHashSet::default(),
|
|
@@ -231,7 +231,7 @@ macro_rules! simple_declaration {
|
|
|
231
231
|
#[derive(Debug)]
|
|
232
232
|
pub struct $name {
|
|
233
233
|
/// The fully qualified name of this declaration
|
|
234
|
-
name:
|
|
234
|
+
name: Box<str>,
|
|
235
235
|
/// The list of definition IDs that compose this declaration
|
|
236
236
|
definition_ids: Vec<DefinitionId>,
|
|
237
237
|
/// The set of references that are made to this declaration
|
|
@@ -244,7 +244,7 @@ macro_rules! simple_declaration {
|
|
|
244
244
|
#[must_use]
|
|
245
245
|
pub fn new(name: String, owner_id: DeclarationId) -> Self {
|
|
246
246
|
Self {
|
|
247
|
-
name,
|
|
247
|
+
name: name.into_boxed_str(),
|
|
248
248
|
definition_ids: Vec::new(),
|
|
249
249
|
references: IdentityHashSet::default(),
|
|
250
250
|
owner_id,
|
|
@@ -608,25 +608,25 @@ impl Namespace {
|
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
namespace_declaration!(Class, ClassDeclaration);
|
|
611
|
-
assert_mem_size!(ClassDeclaration,
|
|
611
|
+
assert_mem_size!(ClassDeclaration, 184);
|
|
612
612
|
namespace_declaration!(Module, ModuleDeclaration);
|
|
613
|
-
assert_mem_size!(ModuleDeclaration,
|
|
613
|
+
assert_mem_size!(ModuleDeclaration, 184);
|
|
614
614
|
namespace_declaration!(SingletonClass, SingletonClassDeclaration);
|
|
615
|
-
assert_mem_size!(SingletonClassDeclaration,
|
|
615
|
+
assert_mem_size!(SingletonClassDeclaration, 184);
|
|
616
616
|
namespace_declaration!(Todo, TodoDeclaration);
|
|
617
|
-
assert_mem_size!(TodoDeclaration,
|
|
617
|
+
assert_mem_size!(TodoDeclaration, 184);
|
|
618
618
|
simple_declaration!(ConstantDeclaration, ConstantReferenceId);
|
|
619
|
-
assert_mem_size!(ConstantDeclaration,
|
|
619
|
+
assert_mem_size!(ConstantDeclaration, 80);
|
|
620
620
|
simple_declaration!(MethodDeclaration, MethodReferenceId);
|
|
621
|
-
assert_mem_size!(MethodDeclaration,
|
|
621
|
+
assert_mem_size!(MethodDeclaration, 80);
|
|
622
622
|
simple_declaration!(GlobalVariableDeclaration, GlobalVariableReferenceId);
|
|
623
|
-
assert_mem_size!(GlobalVariableDeclaration,
|
|
623
|
+
assert_mem_size!(GlobalVariableDeclaration, 80);
|
|
624
624
|
simple_declaration!(InstanceVariableDeclaration, InstanceVariableReferenceId);
|
|
625
|
-
assert_mem_size!(InstanceVariableDeclaration,
|
|
625
|
+
assert_mem_size!(InstanceVariableDeclaration, 80);
|
|
626
626
|
simple_declaration!(ClassVariableDeclaration, ClassVariableReferenceId);
|
|
627
|
-
assert_mem_size!(ClassVariableDeclaration,
|
|
627
|
+
assert_mem_size!(ClassVariableDeclaration, 80);
|
|
628
628
|
simple_declaration!(ConstantAliasDeclaration, ConstantReferenceId);
|
|
629
|
-
assert_mem_size!(ConstantAliasDeclaration,
|
|
629
|
+
assert_mem_size!(ConstantAliasDeclaration, 80);
|
|
630
630
|
|
|
631
631
|
#[cfg(test)]
|
|
632
632
|
mod tests {
|
|
@@ -13,7 +13,10 @@ use crate::model::definitions::{Definition, MethodVisibilityDefinition, Receiver
|
|
|
13
13
|
use crate::model::document::Document;
|
|
14
14
|
use crate::model::encoding::Encoding;
|
|
15
15
|
use crate::model::identity_maps::{IdentityHashMap, IdentityHashSet};
|
|
16
|
-
use crate::model::ids::{
|
|
16
|
+
use crate::model::ids::{
|
|
17
|
+
ConstantReferenceId, DeclarationId, DefinitionId, MethodReferenceId, NameId, StringId, UriId,
|
|
18
|
+
declaration_id_from_lookup_name,
|
|
19
|
+
};
|
|
17
20
|
use crate::model::name::{Name, NameRef, ParentScope, ResolvedName};
|
|
18
21
|
use crate::model::references::{ConstantReference, MethodRef};
|
|
19
22
|
use crate::model::string_ref::StringRef;
|
|
@@ -578,7 +581,7 @@ impl Graph {
|
|
|
578
581
|
|
|
579
582
|
#[must_use]
|
|
580
583
|
pub fn get(&self, name: &str) -> Option<Vec<&Definition>> {
|
|
581
|
-
let declaration_id =
|
|
584
|
+
let declaration_id = declaration_id_from_lookup_name(name);
|
|
582
585
|
let declaration = self.declarations.get(&declaration_id)?;
|
|
583
586
|
|
|
584
587
|
Some(
|
|
@@ -1270,6 +1273,10 @@ impl Graph {
|
|
|
1270
1273
|
let unqualified_str_id = StringId::from(&decl.unqualified_name());
|
|
1271
1274
|
let owner_id = *decl.owner_id();
|
|
1272
1275
|
let is_singleton_class = matches!(decl, Declaration::Namespace(Namespace::SingletonClass(_)));
|
|
1276
|
+
let ancestors_to_detach: Vec<Ancestor> = decl
|
|
1277
|
+
.as_namespace()
|
|
1278
|
+
.map(|ns| ns.ancestors().iter().copied().collect())
|
|
1279
|
+
.unwrap_or_default();
|
|
1273
1280
|
|
|
1274
1281
|
for def_id in def_ids {
|
|
1275
1282
|
self.push_work(Unit::Definition(def_id));
|
|
@@ -1284,6 +1291,16 @@ impl Graph {
|
|
|
1284
1291
|
ns.remove_member(&unqualified_str_id);
|
|
1285
1292
|
}
|
|
1286
1293
|
}
|
|
1294
|
+
|
|
1295
|
+
// Detach from each complete ancestor's descendant set so we don't leave a stale id in descendants
|
|
1296
|
+
for ancestor in ancestors_to_detach {
|
|
1297
|
+
if let Ancestor::Complete(ancestor_id) = ancestor
|
|
1298
|
+
&& let Some(anc_decl) = self.declarations.get_mut(&ancestor_id)
|
|
1299
|
+
&& let Some(ns) = anc_decl.as_namespace_mut()
|
|
1300
|
+
{
|
|
1301
|
+
ns.remove_descendant(&decl_id);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1287
1304
|
}
|
|
1288
1305
|
|
|
1289
1306
|
self.declarations.remove(&decl_id);
|
|
@@ -1570,6 +1587,10 @@ mod tests {
|
|
|
1570
1587
|
assert_members_eq, assert_no_diagnostics, assert_no_members,
|
|
1571
1588
|
};
|
|
1572
1589
|
|
|
1590
|
+
fn definition_ids(definitions: Vec<&Definition>) -> Vec<DefinitionId> {
|
|
1591
|
+
definitions.into_iter().map(Definition::id).collect()
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1573
1594
|
#[test]
|
|
1574
1595
|
fn deleting_a_uri() {
|
|
1575
1596
|
let mut context = GraphTest::new();
|
|
@@ -1992,6 +2013,34 @@ mod tests {
|
|
|
1992
2013
|
assert_eq!(definitions[0].offset().start(), 6);
|
|
1993
2014
|
}
|
|
1994
2015
|
|
|
2016
|
+
#[test]
|
|
2017
|
+
fn get_accepts_leading_double_colon() {
|
|
2018
|
+
let mut context = GraphTest::new();
|
|
2019
|
+
|
|
2020
|
+
context.index_uri("file:///foo.rb", "module Foo; module Bar; end; end");
|
|
2021
|
+
context.resolve();
|
|
2022
|
+
|
|
2023
|
+
// Built-in declarations (root-scoped):
|
|
2024
|
+
let unqualified = context.graph().get("Object").expect("unqualified `Object` lookup");
|
|
2025
|
+
let qualified = context.graph().get("::Object").expect("qualified `::Object` lookup");
|
|
2026
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2027
|
+
|
|
2028
|
+
// Indexed declarations, top-level and nested:
|
|
2029
|
+
let unqualified = context.graph().get("Foo").expect("unqualified `Foo` lookup");
|
|
2030
|
+
let qualified = context.graph().get("::Foo").expect("qualified `::Foo` lookup");
|
|
2031
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2032
|
+
|
|
2033
|
+
let unqualified = context.graph().get("Foo::Bar").expect("unqualified `Foo::Bar` lookup");
|
|
2034
|
+
let qualified = context
|
|
2035
|
+
.graph()
|
|
2036
|
+
.get("::Foo::Bar")
|
|
2037
|
+
.expect("qualified `::Foo::Bar` lookup");
|
|
2038
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2039
|
+
|
|
2040
|
+
// Unknown names still return None when prefixed:
|
|
2041
|
+
assert!(context.graph().get("::DoesNotExist").is_none());
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1995
2044
|
#[test]
|
|
1996
2045
|
fn adding_another_definition_from_a_different_uri() {
|
|
1997
2046
|
let mut context = GraphTest::new();
|
|
@@ -2499,6 +2548,7 @@ mod tests {
|
|
|
2499
2548
|
|
|
2500
2549
|
#[cfg(test)]
|
|
2501
2550
|
mod incremental_resolution_tests {
|
|
2551
|
+
use crate::model::ids::DeclarationId;
|
|
2502
2552
|
use crate::model::name::NameRef;
|
|
2503
2553
|
use crate::test_utils::GraphTest;
|
|
2504
2554
|
use crate::{
|
|
@@ -4126,4 +4176,37 @@ mod incremental_resolution_tests {
|
|
|
4126
4176
|
assert_declaration_exists!(context, "Foo::<Foo>");
|
|
4127
4177
|
assert_declaration_exists!(context, "Bar::<Bar>");
|
|
4128
4178
|
}
|
|
4179
|
+
|
|
4180
|
+
#[test]
|
|
4181
|
+
fn constant_references_through_object_inheritance_are_invalidated() {
|
|
4182
|
+
let mut context = GraphTest::new();
|
|
4183
|
+
context.index_uri("file:///a.rb", "class Foo; end");
|
|
4184
|
+
context.index_uri(
|
|
4185
|
+
"file:///b.rb",
|
|
4186
|
+
r"
|
|
4187
|
+
module Wrap
|
|
4188
|
+
class Foo; end
|
|
4189
|
+
::Foo
|
|
4190
|
+
Foo
|
|
4191
|
+
end
|
|
4192
|
+
",
|
|
4193
|
+
);
|
|
4194
|
+
context.resolve();
|
|
4195
|
+
|
|
4196
|
+
context.delete_uri("file:///a.rb");
|
|
4197
|
+
context.resolve();
|
|
4198
|
+
|
|
4199
|
+
let kernel = context
|
|
4200
|
+
.graph()
|
|
4201
|
+
.declarations()
|
|
4202
|
+
.get(&DeclarationId::from("Kernel"))
|
|
4203
|
+
.unwrap();
|
|
4204
|
+
|
|
4205
|
+
for id in kernel.as_namespace().unwrap().descendants() {
|
|
4206
|
+
assert!(
|
|
4207
|
+
context.graph().declarations().contains_key(id),
|
|
4208
|
+
"Kernel has stale descendant id {id:?} with no backing declaration"
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4129
4212
|
} // mod incremental_resolution_tests
|
|
@@ -9,6 +9,15 @@ pub struct DeclarationMarker;
|
|
|
9
9
|
/// `DeclarationId` represents the ID of a fully qualified name. For example, `Foo::Bar` or `Foo#my_method`
|
|
10
10
|
pub type DeclarationId = Id<DeclarationMarker>;
|
|
11
11
|
|
|
12
|
+
/// Creates a `DeclarationId` from a user-facing lookup name.
|
|
13
|
+
///
|
|
14
|
+
/// Declaration names are stored without the root-scope marker, so `"::Object"`
|
|
15
|
+
/// and `"Object"` must resolve to the same declaration ID.
|
|
16
|
+
#[must_use]
|
|
17
|
+
pub fn declaration_id_from_lookup_name(name: &str) -> DeclarationId {
|
|
18
|
+
DeclarationId::from(name.strip_prefix("::").unwrap_or(name))
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
|
13
22
|
pub struct DefinitionMarker;
|
|
14
23
|
|
|
@@ -84,3 +93,24 @@ assert_mem_size!(ClassVariableReferenceId, 8);
|
|
|
84
93
|
pub struct InstanceVariableMarker;
|
|
85
94
|
pub type InstanceVariableReferenceId = Id<InstanceVariableMarker>;
|
|
86
95
|
assert_mem_size!(InstanceVariableReferenceId, 8);
|
|
96
|
+
|
|
97
|
+
#[cfg(test)]
|
|
98
|
+
mod tests {
|
|
99
|
+
use super::*;
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn declaration_id_from_lookup_name_accepts_root_scope_marker() {
|
|
103
|
+
assert_eq!(
|
|
104
|
+
DeclarationId::from("Object"),
|
|
105
|
+
declaration_id_from_lookup_name("::Object")
|
|
106
|
+
);
|
|
107
|
+
assert_eq!(
|
|
108
|
+
DeclarationId::from("Foo::Bar"),
|
|
109
|
+
declaration_id_from_lookup_name("::Foo::Bar")
|
|
110
|
+
);
|
|
111
|
+
assert_ne!(
|
|
112
|
+
DeclarationId::from("Object"),
|
|
113
|
+
declaration_id_from_lookup_name("::::Object")
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -9,15 +9,23 @@ use std::ops::Deref;
|
|
|
9
9
|
/// graph when its count reaches zero.
|
|
10
10
|
#[derive(Debug)]
|
|
11
11
|
pub struct StringRef {
|
|
12
|
-
value:
|
|
12
|
+
value: Box<str>,
|
|
13
13
|
ref_count: u32,
|
|
14
14
|
}
|
|
15
|
-
assert_mem_size!(StringRef,
|
|
15
|
+
assert_mem_size!(StringRef, 24);
|
|
16
16
|
|
|
17
17
|
impl StringRef {
|
|
18
18
|
#[must_use]
|
|
19
19
|
pub fn new(value: String) -> Self {
|
|
20
|
-
Self {
|
|
20
|
+
Self {
|
|
21
|
+
value: value.into_boxed_str(),
|
|
22
|
+
ref_count: 1,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[must_use]
|
|
27
|
+
pub fn as_str(&self) -> &str {
|
|
28
|
+
&self.value
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
#[must_use]
|
|
@@ -44,7 +52,7 @@ impl StringRef {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
impl Deref for StringRef {
|
|
47
|
-
type Target =
|
|
55
|
+
type Target = str;
|
|
48
56
|
|
|
49
57
|
fn deref(&self) -> &Self::Target {
|
|
50
58
|
&self.value
|