rubydex 0.1.0.beta12-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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +125 -0
  4. data/THIRD_PARTY_LICENSES.html +4562 -0
  5. data/exe/rdx +47 -0
  6. data/ext/rubydex/declaration.c +453 -0
  7. data/ext/rubydex/declaration.h +23 -0
  8. data/ext/rubydex/definition.c +284 -0
  9. data/ext/rubydex/definition.h +28 -0
  10. data/ext/rubydex/diagnostic.c +6 -0
  11. data/ext/rubydex/diagnostic.h +11 -0
  12. data/ext/rubydex/document.c +97 -0
  13. data/ext/rubydex/document.h +10 -0
  14. data/ext/rubydex/extconf.rb +138 -0
  15. data/ext/rubydex/graph.c +681 -0
  16. data/ext/rubydex/graph.h +10 -0
  17. data/ext/rubydex/handle.h +44 -0
  18. data/ext/rubydex/location.c +22 -0
  19. data/ext/rubydex/location.h +15 -0
  20. data/ext/rubydex/reference.c +123 -0
  21. data/ext/rubydex/reference.h +15 -0
  22. data/ext/rubydex/rubydex.c +22 -0
  23. data/ext/rubydex/utils.c +108 -0
  24. data/ext/rubydex/utils.h +34 -0
  25. data/lib/rubydex/3.2/rubydex.so +0 -0
  26. data/lib/rubydex/3.3/rubydex.so +0 -0
  27. data/lib/rubydex/3.4/rubydex.so +0 -0
  28. data/lib/rubydex/4.0/rubydex.so +0 -0
  29. data/lib/rubydex/comment.rb +17 -0
  30. data/lib/rubydex/diagnostic.rb +21 -0
  31. data/lib/rubydex/failures.rb +15 -0
  32. data/lib/rubydex/graph.rb +98 -0
  33. data/lib/rubydex/keyword.rb +17 -0
  34. data/lib/rubydex/keyword_parameter.rb +13 -0
  35. data/lib/rubydex/librubydex_sys.so +0 -0
  36. data/lib/rubydex/location.rb +90 -0
  37. data/lib/rubydex/mixin.rb +22 -0
  38. data/lib/rubydex/version.rb +5 -0
  39. data/lib/rubydex.rb +23 -0
  40. data/rbi/rubydex.rbi +422 -0
  41. data/rust/Cargo.lock +1851 -0
  42. data/rust/Cargo.toml +29 -0
  43. data/rust/about.hbs +78 -0
  44. data/rust/about.toml +10 -0
  45. data/rust/rubydex/Cargo.toml +42 -0
  46. data/rust/rubydex/src/compile_assertions.rs +13 -0
  47. data/rust/rubydex/src/diagnostic.rs +110 -0
  48. data/rust/rubydex/src/errors.rs +28 -0
  49. data/rust/rubydex/src/indexing/local_graph.rs +224 -0
  50. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
  51. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
  52. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  53. data/rust/rubydex/src/indexing.rs +210 -0
  54. data/rust/rubydex/src/integrity.rs +279 -0
  55. data/rust/rubydex/src/job_queue.rs +205 -0
  56. data/rust/rubydex/src/lib.rs +17 -0
  57. data/rust/rubydex/src/listing.rs +371 -0
  58. data/rust/rubydex/src/main.rs +160 -0
  59. data/rust/rubydex/src/model/built_in.rs +83 -0
  60. data/rust/rubydex/src/model/comment.rs +24 -0
  61. data/rust/rubydex/src/model/declaration.rs +671 -0
  62. data/rust/rubydex/src/model/definitions.rs +1682 -0
  63. data/rust/rubydex/src/model/document.rs +222 -0
  64. data/rust/rubydex/src/model/encoding.rs +22 -0
  65. data/rust/rubydex/src/model/graph.rs +3754 -0
  66. data/rust/rubydex/src/model/id.rs +110 -0
  67. data/rust/rubydex/src/model/identity_maps.rs +58 -0
  68. data/rust/rubydex/src/model/ids.rs +60 -0
  69. data/rust/rubydex/src/model/keywords.rs +256 -0
  70. data/rust/rubydex/src/model/name.rs +298 -0
  71. data/rust/rubydex/src/model/references.rs +111 -0
  72. data/rust/rubydex/src/model/string_ref.rs +50 -0
  73. data/rust/rubydex/src/model/visibility.rs +41 -0
  74. data/rust/rubydex/src/model.rs +15 -0
  75. data/rust/rubydex/src/offset.rs +147 -0
  76. data/rust/rubydex/src/position.rs +6 -0
  77. data/rust/rubydex/src/query.rs +1841 -0
  78. data/rust/rubydex/src/resolution.rs +6517 -0
  79. data/rust/rubydex/src/stats/memory.rs +71 -0
  80. data/rust/rubydex/src/stats/orphan_report.rs +264 -0
  81. data/rust/rubydex/src/stats/timer.rs +127 -0
  82. data/rust/rubydex/src/stats.rs +11 -0
  83. data/rust/rubydex/src/test_utils/context.rs +226 -0
  84. data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
  86. data/rust/rubydex/src/test_utils.rs +52 -0
  87. data/rust/rubydex/src/visualization/dot.rs +192 -0
  88. data/rust/rubydex/src/visualization.rs +6 -0
  89. data/rust/rubydex/tests/cli.rs +185 -0
  90. data/rust/rubydex-mcp/Cargo.toml +28 -0
  91. data/rust/rubydex-mcp/src/main.rs +48 -0
  92. data/rust/rubydex-mcp/src/server.rs +1145 -0
  93. data/rust/rubydex-mcp/src/tools.rs +49 -0
  94. data/rust/rubydex-mcp/tests/mcp.rs +302 -0
  95. data/rust/rubydex-sys/Cargo.toml +20 -0
  96. data/rust/rubydex-sys/build.rs +14 -0
  97. data/rust/rubydex-sys/cbindgen.toml +12 -0
  98. data/rust/rubydex-sys/src/declaration_api.rs +485 -0
  99. data/rust/rubydex-sys/src/definition_api.rs +443 -0
  100. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  101. data/rust/rubydex-sys/src/document_api.rs +85 -0
  102. data/rust/rubydex-sys/src/graph_api.rs +948 -0
  103. data/rust/rubydex-sys/src/lib.rs +79 -0
  104. data/rust/rubydex-sys/src/location_api.rs +79 -0
  105. data/rust/rubydex-sys/src/name_api.rs +135 -0
  106. data/rust/rubydex-sys/src/reference_api.rs +267 -0
  107. data/rust/rubydex-sys/src/utils.rs +70 -0
  108. data/rust/rustfmt.toml +2 -0
  109. metadata +159 -0
@@ -0,0 +1,948 @@
1
+ //! This file provides the C API for the Graph object
2
+
3
+ use crate::declaration_api::CDeclaration;
4
+ use crate::declaration_api::DeclarationsIter;
5
+ use crate::document_api::DocumentsIter;
6
+ use crate::reference_api::{CConstantReference, CMethodReference, ConstantReferencesIter, MethodReferencesIter};
7
+ use crate::{name_api, utils};
8
+ use libc::{c_char, c_void};
9
+ use rubydex::indexing::LanguageId;
10
+ use rubydex::model::encoding::Encoding;
11
+ use rubydex::model::graph::Graph;
12
+ use rubydex::model::ids::{DeclarationId, NameId};
13
+ use rubydex::model::name::NameRef;
14
+ use rubydex::query::{CompletionCandidate, CompletionContext, CompletionReceiver};
15
+ use rubydex::resolution::Resolver;
16
+ use rubydex::{indexing, integrity, listing, query};
17
+ use std::ffi::CString;
18
+ use std::path::PathBuf;
19
+ use std::{mem, ptr};
20
+
21
+ pub type GraphPointer = *mut c_void;
22
+
23
+ /// Creates a new graph within a mutex. This is meant to be used when creating new Graph objects in Ruby
24
+ #[unsafe(no_mangle)]
25
+ pub extern "C" fn rdx_graph_new() -> GraphPointer {
26
+ Box::into_raw(Box::new(Graph::new())) as GraphPointer
27
+ }
28
+
29
+ /// Frees a Graph through its pointer
30
+ #[unsafe(no_mangle)]
31
+ pub extern "C" fn rdx_graph_free(pointer: GraphPointer) {
32
+ unsafe {
33
+ let _ = Box::from_raw(pointer.cast::<Graph>());
34
+ }
35
+ }
36
+
37
+ pub fn with_graph<F, T>(pointer: GraphPointer, action: F) -> T
38
+ where
39
+ F: FnOnce(&Graph) -> T,
40
+ {
41
+ let mut graph = unsafe { Box::from_raw(pointer.cast::<Graph>()) };
42
+ let result = action(&mut graph);
43
+ mem::forget(graph);
44
+ result
45
+ }
46
+
47
+ fn with_mut_graph<F, T>(pointer: GraphPointer, action: F) -> T
48
+ where
49
+ F: FnOnce(&mut Graph) -> T,
50
+ {
51
+ let mut graph = unsafe { Box::from_raw(pointer.cast::<Graph>()) };
52
+ let result = action(&mut graph);
53
+ mem::forget(graph);
54
+ result
55
+ }
56
+
57
+ /// Searches the graph using exact substring matching
58
+ ///
59
+ /// # Safety
60
+ ///
61
+ /// Expects both the graph and the query pointers to be valid
62
+ #[unsafe(no_mangle)]
63
+ pub unsafe extern "C" fn rdx_graph_declarations_search(
64
+ pointer: GraphPointer,
65
+ c_query: *const c_char,
66
+ ) -> *mut DeclarationsIter {
67
+ {
68
+ let Ok(query) = (unsafe { utils::convert_char_ptr_to_string(c_query) }) else {
69
+ return ptr::null_mut();
70
+ };
71
+
72
+ let entries = with_graph(pointer, |graph| {
73
+ query::declaration_search(graph, &query, &query::MatchMode::Exact)
74
+ .into_iter()
75
+ .filter_map(|id| {
76
+ let decl = graph.declarations().get(&id)?;
77
+ Some(CDeclaration::from_declaration(id, decl))
78
+ })
79
+ .collect::<Vec<CDeclaration>>()
80
+ .into_boxed_slice()
81
+ });
82
+
83
+ DeclarationsIter::new(entries)
84
+ }
85
+ }
86
+
87
+ /// Searches the graph using fuzzy matching
88
+ ///
89
+ /// # Safety
90
+ ///
91
+ /// Expects both the graph and the query pointers to be valid
92
+ #[unsafe(no_mangle)]
93
+ pub unsafe extern "C" fn rdx_graph_declarations_fuzzy_search(
94
+ pointer: GraphPointer,
95
+ c_query: *const c_char,
96
+ ) -> *mut DeclarationsIter {
97
+ {
98
+ let Ok(query) = (unsafe { utils::convert_char_ptr_to_string(c_query) }) else {
99
+ return ptr::null_mut();
100
+ };
101
+
102
+ let entries = with_graph(pointer, |graph| {
103
+ query::declaration_search(graph, &query, &query::MatchMode::Fuzzy)
104
+ .into_iter()
105
+ .filter_map(|id| {
106
+ let decl = graph.declarations().get(&id)?;
107
+ Some(CDeclaration::from_declaration(id, decl))
108
+ })
109
+ .collect::<Vec<CDeclaration>>()
110
+ .into_boxed_slice()
111
+ });
112
+
113
+ DeclarationsIter::new(entries)
114
+ }
115
+ }
116
+
117
+ /// # Panics
118
+ ///
119
+ /// Will panic if the nesting cannot be transformed into a vector of strings
120
+ ///
121
+ /// # Safety
122
+ ///
123
+ /// Assumes that the `const_name` and `nesting` pointer are valid
124
+ #[unsafe(no_mangle)]
125
+ pub unsafe extern "C" fn rdx_graph_resolve_constant(
126
+ pointer: GraphPointer,
127
+ const_name: *const c_char,
128
+ nesting: *const *const c_char,
129
+ count: usize,
130
+ ) -> *const CDeclaration {
131
+ with_mut_graph(pointer, |graph| {
132
+ let nesting: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(nesting, count).unwrap() };
133
+ let const_name: String = unsafe { utils::convert_char_ptr_to_string(const_name).unwrap() };
134
+ let (name_id, names_to_untrack) = name_api::nesting_stack_to_name_id(graph, &const_name, nesting);
135
+
136
+ let mut resolver = Resolver::new(graph);
137
+
138
+ let declaration = match resolver.resolve_constant(name_id) {
139
+ Some(id) => {
140
+ let decl = graph.declarations().get(&id).unwrap();
141
+ Box::into_raw(Box::new(CDeclaration::from_declaration(id, decl))).cast_const()
142
+ }
143
+ None => ptr::null(),
144
+ };
145
+
146
+ for name_id in names_to_untrack {
147
+ graph.untrack_name(name_id);
148
+ }
149
+
150
+ declaration
151
+ })
152
+ }
153
+
154
+ /// Adds paths to exclude from file discovery during indexing.
155
+ ///
156
+ /// # Panics
157
+ ///
158
+ /// Will panic if the given array of C string paths cannot be converted to a `Vec<String>`.
159
+ ///
160
+ /// # Safety
161
+ ///
162
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
163
+ /// - `paths` must be an array of `count` valid, null-terminated UTF-8 strings.
164
+ #[unsafe(no_mangle)]
165
+ pub unsafe extern "C" fn rdx_graph_exclude_paths(pointer: GraphPointer, paths: *const *const c_char, count: usize) {
166
+ let paths: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(paths, count).unwrap() };
167
+ let path_bufs: Vec<PathBuf> = paths.into_iter().map(PathBuf::from).collect();
168
+ with_mut_graph(pointer, |graph| graph.exclude_paths(path_bufs));
169
+ }
170
+
171
+ /// Returns the currently excluded paths as an array of C strings. Writes the count to `out_count`. Returns NULL if no
172
+ /// paths are excluded. Caller must free with `free_c_string_array`.
173
+ ///
174
+ /// # Safety
175
+ ///
176
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
177
+ /// - `out_count` must be a valid, writable pointer.
178
+ #[unsafe(no_mangle)]
179
+ pub unsafe extern "C" fn rdx_graph_excluded_paths(
180
+ pointer: GraphPointer,
181
+ out_count: *mut usize,
182
+ ) -> *const *const c_char {
183
+ with_graph(pointer, |graph| {
184
+ let excluded = graph.excluded_paths();
185
+
186
+ if excluded.is_empty() {
187
+ unsafe { *out_count = 0 };
188
+ return ptr::null();
189
+ }
190
+
191
+ let c_strings: Vec<*const c_char> = excluded
192
+ .iter()
193
+ .filter_map(|path| {
194
+ CString::new(path.to_string_lossy().as_ref())
195
+ .ok()
196
+ .map(|c_string| c_string.into_raw().cast_const())
197
+ })
198
+ .collect();
199
+
200
+ unsafe { *out_count = c_strings.len() };
201
+
202
+ let boxed = c_strings.into_boxed_slice();
203
+ Box::into_raw(boxed).cast::<*const c_char>()
204
+ })
205
+ }
206
+
207
+ /// Indexes all given file paths in parallel using the provided Graph pointer.
208
+ /// Returns an array of error message strings and writes the count to `out_error_count`.
209
+ /// Returns NULL if there are no errors. Caller must free with `free_c_string_array`.
210
+ ///
211
+ /// # Panics
212
+ ///
213
+ /// Will panic if the given array of C string file paths cannot be converted to a Vec<String>
214
+ ///
215
+ /// # Safety
216
+ ///
217
+ /// This function is unsafe because it dereferences raw pointers coming from C. The caller has to ensure that the Ruby
218
+ /// VM will not free the pointers related to the string array while they are in use by Rust
219
+ #[unsafe(no_mangle)]
220
+ pub unsafe extern "C" fn rdx_index_all(
221
+ pointer: GraphPointer,
222
+ file_paths: *const *const c_char,
223
+ count: usize,
224
+ out_error_count: *mut usize,
225
+ ) -> *const *const c_char {
226
+ let file_paths: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(file_paths, count).unwrap() };
227
+
228
+ with_mut_graph(pointer, |graph| {
229
+ let (file_paths, listing_errors) = listing::collect_file_paths(file_paths, graph.excluded_paths());
230
+ let indexing_errors = indexing::index_files(graph, file_paths);
231
+
232
+ let all_errors: Vec<String> = listing_errors
233
+ .into_iter()
234
+ .chain(indexing_errors)
235
+ .map(|e| e.to_string())
236
+ .collect();
237
+
238
+ if all_errors.is_empty() {
239
+ unsafe { *out_error_count = 0 };
240
+ return ptr::null();
241
+ }
242
+
243
+ let c_strings: Vec<*const c_char> = all_errors
244
+ .into_iter()
245
+ .filter_map(|string| {
246
+ CString::new(string)
247
+ .ok()
248
+ .map(|c_string| c_string.into_raw().cast_const())
249
+ })
250
+ .collect();
251
+
252
+ unsafe { *out_error_count = c_strings.len() };
253
+
254
+ let boxed = c_strings.into_boxed_slice();
255
+ Box::into_raw(boxed).cast::<*const c_char>()
256
+ })
257
+ }
258
+
259
+ /// Deletes a document and all of its definitions from the graph.
260
+ /// Returns a pointer to the URI ID if the document was found and removed, or NULL if it didn't exist.
261
+ /// Caller must free the returned pointer with `free_u64`.
262
+ ///
263
+ /// # Safety
264
+ ///
265
+ /// Expects both the graph pointer and uri string pointer to be valid
266
+ #[unsafe(no_mangle)]
267
+ pub unsafe extern "C" fn rdx_graph_delete_document(pointer: GraphPointer, uri: *const c_char) -> *const u64 {
268
+ let Ok(uri_str) = (unsafe { utils::convert_char_ptr_to_string(uri) }) else {
269
+ return ptr::null();
270
+ };
271
+
272
+ with_mut_graph(pointer, |graph| match graph.delete_document(&uri_str) {
273
+ Some(uri_id) => Box::into_raw(Box::new(*uri_id)),
274
+ None => ptr::null(),
275
+ })
276
+ }
277
+
278
+ /// Runs the resolver to compute declarations, ownership and related structures
279
+ #[unsafe(no_mangle)]
280
+ pub extern "C" fn rdx_graph_resolve(pointer: GraphPointer) {
281
+ with_mut_graph(pointer, |graph| {
282
+ let mut resolver = Resolver::new(graph);
283
+ resolver.resolve();
284
+ });
285
+ }
286
+
287
+ /// Checks the integrity of the graph and returns an array of error message strings. Returns NULL if there are no
288
+ /// errors. Caller must free with `free_c_string_array`.
289
+ ///
290
+ /// # Safety
291
+ ///
292
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
293
+ /// - `out_error_count` must be a valid, writable pointer.
294
+ #[unsafe(no_mangle)]
295
+ pub unsafe extern "C" fn rdx_check_integrity(
296
+ pointer: GraphPointer,
297
+ out_error_count: *mut usize,
298
+ ) -> *const *const c_char {
299
+ with_graph(pointer, |graph| {
300
+ let errors = integrity::check_integrity(graph);
301
+
302
+ if errors.is_empty() {
303
+ unsafe { *out_error_count = 0 };
304
+ return ptr::null();
305
+ }
306
+
307
+ let c_strings: Vec<*const c_char> = errors
308
+ .into_iter()
309
+ .filter_map(|error| {
310
+ CString::new(error.to_string())
311
+ .ok()
312
+ .map(|c_string| c_string.into_raw().cast_const())
313
+ })
314
+ .collect();
315
+
316
+ unsafe { *out_error_count = c_strings.len() };
317
+
318
+ let boxed = c_strings.into_boxed_slice();
319
+ Box::into_raw(boxed).cast::<*const c_char>()
320
+ })
321
+ }
322
+
323
+ /// # Safety
324
+ ///
325
+ /// Expects both the graph pointer and encoding string pointer to be valid
326
+ #[unsafe(no_mangle)]
327
+ pub unsafe extern "C" fn rdx_graph_set_encoding(pointer: GraphPointer, encoding_str: *const c_char) -> bool {
328
+ let Ok(encoding) = (unsafe { utils::convert_char_ptr_to_string(encoding_str) }) else {
329
+ return false;
330
+ };
331
+
332
+ let encoding_variant = match encoding.as_str() {
333
+ "utf8" => Encoding::Utf8,
334
+ "utf16" => Encoding::Utf16,
335
+ "utf32" => Encoding::Utf32,
336
+ _ => {
337
+ return false;
338
+ }
339
+ };
340
+
341
+ with_mut_graph(pointer, |graph| {
342
+ graph.set_encoding(encoding_variant);
343
+ });
344
+
345
+ true
346
+ }
347
+
348
+ /// Creates a new iterator over declaration IDs by snapshotting the current set of IDs.
349
+ ///
350
+ /// # Safety
351
+ ///
352
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
353
+ /// - The returned pointer must be freed with `rdx_graph_declarations_iter_free`.
354
+ #[unsafe(no_mangle)]
355
+ pub unsafe extern "C" fn rdx_graph_declarations_iter_new(pointer: GraphPointer) -> *mut DeclarationsIter {
356
+ // Snapshot the declarations at iterator creation to avoid borrowing across FFI calls
357
+ let entries = with_graph(pointer, |graph| {
358
+ graph
359
+ .declarations()
360
+ .iter()
361
+ .map(|(id, decl)| CDeclaration::from_declaration(*id, decl))
362
+ .collect::<Vec<CDeclaration>>()
363
+ .into_boxed_slice()
364
+ });
365
+
366
+ DeclarationsIter::new(entries)
367
+ }
368
+
369
+ /// Creates a new iterator over document (URI) IDs by snapshotting the current set of IDs.
370
+ ///
371
+ /// # Safety
372
+ ///
373
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
374
+ /// - The returned pointer must be freed with `rdx_graph_documents_iter_free`.
375
+ #[unsafe(no_mangle)]
376
+ pub unsafe extern "C" fn rdx_graph_documents_iter_new(pointer: GraphPointer) -> *mut DocumentsIter {
377
+ let entries = with_graph(pointer, |graph| {
378
+ graph
379
+ .documents()
380
+ .keys()
381
+ .map(|uri_id| **uri_id)
382
+ .collect::<Vec<_>>()
383
+ .into_boxed_slice()
384
+ });
385
+
386
+ DocumentsIter::new(entries)
387
+ }
388
+
389
+ /// Attempts to resolve a declaration from a fully-qualified name string.
390
+ /// Returns a `CDeclaration` pointer if it exists, or NULL if it does not.
391
+ ///
392
+ /// # Safety
393
+ /// - `pointer` must be a valid `GraphPointer`
394
+ /// - `name` must be a valid, null-terminated UTF-8 string
395
+ #[unsafe(no_mangle)]
396
+ pub unsafe extern "C" fn rdx_graph_get_declaration(pointer: GraphPointer, name: *const c_char) -> *const CDeclaration {
397
+ let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
398
+ return ptr::null();
399
+ };
400
+
401
+ with_graph(pointer, |graph| {
402
+ let decl_id = DeclarationId::from(name_str.as_str());
403
+
404
+ if let Some(decl) = graph.declarations().get(&decl_id) {
405
+ Box::into_raw(Box::new(CDeclaration::from_declaration(decl_id, decl))).cast_const()
406
+ } else {
407
+ ptr::null()
408
+ }
409
+ })
410
+ }
411
+
412
+ /// Creates a new iterator over constant references by snapshotting the current set of IDs.
413
+ ///
414
+ /// # Safety
415
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
416
+ #[unsafe(no_mangle)]
417
+ pub unsafe extern "C" fn rdx_graph_constant_references_iter_new(pointer: GraphPointer) -> *mut ConstantReferencesIter {
418
+ with_graph(pointer, |graph| {
419
+ let refs: Vec<_> = graph
420
+ .constant_references()
421
+ .iter()
422
+ .map(|(id, cref)| {
423
+ let declaration_id = graph
424
+ .names()
425
+ .get(cref.name_id())
426
+ .and_then(|name_ref| match name_ref {
427
+ NameRef::Resolved(resolved) => Some(**resolved.declaration_id()),
428
+ NameRef::Unresolved(_) => None,
429
+ })
430
+ .unwrap_or(0);
431
+
432
+ CConstantReference {
433
+ id: **id,
434
+ declaration_id,
435
+ }
436
+ })
437
+ .collect();
438
+
439
+ ConstantReferencesIter::new(refs.into_boxed_slice())
440
+ })
441
+ }
442
+
443
+ /// Creates a new iterator over method references by snapshotting the current set of IDs.
444
+ ///
445
+ /// # Safety
446
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
447
+ #[unsafe(no_mangle)]
448
+ pub unsafe extern "C" fn rdx_graph_method_references_iter_new(pointer: GraphPointer) -> *mut MethodReferencesIter {
449
+ with_graph(pointer, |graph| {
450
+ let refs: Vec<_> = graph
451
+ .method_references()
452
+ .keys()
453
+ .map(|id| CMethodReference { id: **id })
454
+ .collect();
455
+
456
+ MethodReferencesIter::new(refs.into_boxed_slice())
457
+ })
458
+ }
459
+
460
+ /// Resolves a require path to its document URI ID.
461
+ /// Returns a pointer to the URI ID if found, or NULL if not found.
462
+ /// Caller must free with the returned pointer.
463
+ ///
464
+ /// # Safety
465
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
466
+ /// - `require_path` must be a valid, null-terminated UTF-8 string.
467
+ /// - `load_paths` must be an array of `load_paths_count` valid, null-terminated UTF-8 strings.
468
+ #[unsafe(no_mangle)]
469
+ pub unsafe extern "C" fn rdx_resolve_require_path(
470
+ pointer: GraphPointer,
471
+ require_path: *const c_char,
472
+ load_paths: *const *const c_char,
473
+ load_paths_count: usize,
474
+ ) -> *const u64 {
475
+ let Ok(path_str) = (unsafe { utils::convert_char_ptr_to_string(require_path) }) else {
476
+ return ptr::null();
477
+ };
478
+
479
+ let Ok(paths_vec) = (unsafe { utils::convert_double_pointer_to_vec(load_paths, load_paths_count) }) else {
480
+ return ptr::null();
481
+ };
482
+ let paths_vec = paths_vec.into_iter().map(PathBuf::from).collect::<Vec<_>>();
483
+
484
+ with_graph(pointer, |graph| {
485
+ query::resolve_require_path(graph, &path_str, &paths_vec).map_or(ptr::null(), |id| Box::into_raw(Box::new(*id)))
486
+ })
487
+ }
488
+
489
+ /// Returns all require paths for completion.
490
+ /// Returns array of C strings and writes count to `out_count`.
491
+ /// Returns null if `load_path` contain invalid UTF-8.
492
+ /// Caller must free with `free_c_string_array`.
493
+ ///
494
+ /// # Safety
495
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
496
+ /// - `load_path` must be an array of `load_path_count` valid, null-terminated UTF-8 strings.
497
+ /// - `out_count` must be a valid, writable pointer.
498
+ #[unsafe(no_mangle)]
499
+ pub unsafe extern "C" fn rdx_require_paths(
500
+ pointer: GraphPointer,
501
+ load_path: *const *const c_char,
502
+ load_path_count: usize,
503
+ out_count: *mut usize,
504
+ ) -> *const *const c_char {
505
+ let Ok(paths_vec) = (unsafe { utils::convert_double_pointer_to_vec(load_path, load_path_count) }) else {
506
+ return ptr::null_mut();
507
+ };
508
+ let paths_vec = paths_vec.into_iter().map(PathBuf::from).collect::<Vec<_>>();
509
+
510
+ let results = with_graph(pointer, |graph| query::require_paths(graph, &paths_vec));
511
+
512
+ let c_strings: Vec<*const c_char> = results
513
+ .into_iter()
514
+ .filter_map(|string| {
515
+ CString::new(string)
516
+ .ok()
517
+ .map(|c_string| c_string.into_raw().cast_const())
518
+ })
519
+ .collect();
520
+
521
+ unsafe { *out_count = c_strings.len() };
522
+
523
+ let boxed = c_strings.into_boxed_slice();
524
+ Box::into_raw(boxed).cast::<*const c_char>()
525
+ }
526
+
527
+ #[repr(C)]
528
+ pub enum IndexSourceResult {
529
+ Success = 0,
530
+ InvalidUri = 1,
531
+ InvalidSource = 2,
532
+ InvalidLanguageId = 3,
533
+ UnsupportedLanguageId = 4,
534
+ }
535
+
536
+ /// Indexes source code from memory using the specified language. Returns `IndexSourceResult::Success` on success
537
+ /// or a specific error variant if string conversion or language lookup fails.
538
+ ///
539
+ /// # Safety
540
+ ///
541
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
542
+ /// - `uri` and `language_id` must be valid, null-terminated UTF-8 strings.
543
+ /// - `source` must point to a valid UTF-8 byte buffer of at least `source_len` bytes.
544
+ /// It may contain null bytes.
545
+ #[unsafe(no_mangle)]
546
+ pub unsafe extern "C" fn rdx_index_source(
547
+ pointer: GraphPointer,
548
+ uri: *const c_char,
549
+ source: *const c_char,
550
+ source_len: usize,
551
+ language_id: *const c_char,
552
+ ) -> IndexSourceResult {
553
+ let Ok(uri_str) = (unsafe { utils::convert_char_ptr_to_string(uri) }) else {
554
+ return IndexSourceResult::InvalidUri;
555
+ };
556
+
557
+ let source_bytes = unsafe { std::slice::from_raw_parts(source.cast::<u8>(), source_len) };
558
+ let Ok(source_str) = std::str::from_utf8(source_bytes) else {
559
+ return IndexSourceResult::InvalidSource;
560
+ };
561
+
562
+ let Ok(language_id_str) = (unsafe { utils::convert_char_ptr_to_string(language_id) }) else {
563
+ return IndexSourceResult::InvalidLanguageId;
564
+ };
565
+
566
+ let Ok(language) = LanguageId::from_language_id(&language_id_str) else {
567
+ return IndexSourceResult::UnsupportedLanguageId;
568
+ };
569
+
570
+ with_mut_graph(pointer, |graph| {
571
+ indexing::index_source(graph, &uri_str, source_str, &language);
572
+ IndexSourceResult::Success
573
+ })
574
+ }
575
+
576
+ #[repr(C)]
577
+ #[derive(Debug, Clone, Copy)]
578
+ pub enum CCompletionCandidateKind {
579
+ Declaration = 0,
580
+ Keyword = 1,
581
+ KeywordParameter = 2,
582
+ }
583
+
584
+ #[repr(C)]
585
+ pub struct CCompletionCandidate {
586
+ pub kind: CCompletionCandidateKind,
587
+ /// Only valid when `kind == Declaration`; null otherwise.
588
+ pub declaration: *const CDeclaration,
589
+ pub name: *const c_char,
590
+ pub documentation: *const c_char,
591
+ }
592
+
593
+ #[repr(C)]
594
+ pub struct CompletionCandidateArray {
595
+ pub items: *mut CCompletionCandidate,
596
+ pub len: usize,
597
+ }
598
+
599
+ impl CompletionCandidateArray {
600
+ fn from_vec(entries: Vec<CCompletionCandidate>) -> *mut CompletionCandidateArray {
601
+ let mut boxed = entries.into_boxed_slice();
602
+ let len = boxed.len();
603
+ let ptr = boxed.as_mut_ptr();
604
+ mem::forget(boxed);
605
+ Box::into_raw(Box::new(CompletionCandidateArray { items: ptr, len }))
606
+ }
607
+ }
608
+
609
+ /// Frees a completion candidate array previously returned by a completion function.
610
+ ///
611
+ /// # Safety
612
+ ///
613
+ /// - `ptr` must be a valid pointer previously returned by a completion function.
614
+ /// - `ptr` must not be used after being freed.
615
+ #[unsafe(no_mangle)]
616
+ pub unsafe extern "C" fn rdx_completion_candidates_free(ptr: *mut CompletionCandidateArray) {
617
+ if ptr.is_null() {
618
+ return;
619
+ }
620
+
621
+ let array = unsafe { Box::from_raw(ptr) };
622
+
623
+ if !array.items.is_null() && array.len > 0 {
624
+ let slice_ptr = ptr::slice_from_raw_parts_mut(array.items, array.len);
625
+ let mut boxed_slice: Box<[CCompletionCandidate]> = unsafe { Box::from_raw(slice_ptr) };
626
+
627
+ for entry in &mut *boxed_slice {
628
+ if !entry.declaration.is_null() {
629
+ let _ = unsafe { Box::from_raw(entry.declaration.cast_mut()) };
630
+ }
631
+ if !entry.name.is_null() {
632
+ let _ = unsafe { CString::from_raw(entry.name.cast_mut()) };
633
+ }
634
+ if !entry.documentation.is_null() {
635
+ let _ = unsafe { CString::from_raw(entry.documentation.cast_mut()) };
636
+ }
637
+ }
638
+ }
639
+ }
640
+
641
+ /// Converts the nesting stack into a `NameId`.
642
+ /// The last element of the nesting stack is treated as the self type; if the stack is empty, `"Object"` is used.
643
+ ///
644
+ /// Returns `Err` if the nesting array contains invalid UTF-8.
645
+ ///
646
+ /// # Safety
647
+ ///
648
+ /// `nesting` must point to `nesting_count` valid, null-terminated UTF-8 strings.
649
+ unsafe fn completion_nesting_name_id(
650
+ graph: &mut Graph,
651
+ nesting: *const *const c_char,
652
+ nesting_count: usize,
653
+ ) -> Result<(NameId, Vec<NameId>), std::str::Utf8Error> {
654
+ let mut nesting: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(nesting, nesting_count)? };
655
+
656
+ // When serving completion in a bare script, the self (top level) context is Object
657
+ let self_name = if nesting.is_empty() {
658
+ "Object".to_string()
659
+ } else {
660
+ nesting.pop().unwrap()
661
+ };
662
+
663
+ Ok(name_api::nesting_stack_to_name_id(graph, &self_name, nesting))
664
+ }
665
+
666
+ /// The result of a completion operation, carrying either a candidate array or an error message.
667
+ #[repr(C)]
668
+ pub struct CompletionResult {
669
+ /// Non-null on success; null on error.
670
+ pub candidates: *mut CompletionCandidateArray,
671
+ /// Non-null on error; null on success. Caller must free with `free_c_string`.
672
+ pub error: *const c_char,
673
+ }
674
+
675
+ impl CompletionResult {
676
+ fn success(candidates: *mut CompletionCandidateArray) -> Self {
677
+ Self {
678
+ candidates,
679
+ error: ptr::null(),
680
+ }
681
+ }
682
+
683
+ fn error(message: &str) -> Self {
684
+ Self {
685
+ candidates: ptr::null_mut(),
686
+ error: CString::new(message)
687
+ .map(|s| s.into_raw().cast_const())
688
+ .unwrap_or(ptr::null()),
689
+ }
690
+ }
691
+ }
692
+
693
+ /// Runs completion for the given receiver and returns a structured result with candidates or an error message
694
+ fn run_and_finalize_completion(
695
+ graph: &mut Graph,
696
+ receiver: CompletionReceiver,
697
+ names_to_untrack: Vec<NameId>,
698
+ ) -> CompletionResult {
699
+ let candidates = match query::completion_candidates(graph, CompletionContext::new(receiver)) {
700
+ Ok(candidates) => candidates,
701
+ Err(e) => {
702
+ for name_id in names_to_untrack {
703
+ graph.untrack_name(name_id);
704
+ }
705
+ return CompletionResult::error(&e.to_string());
706
+ }
707
+ };
708
+
709
+ let entries: Vec<CCompletionCandidate> = candidates
710
+ .into_iter()
711
+ .map(|candidate| match candidate {
712
+ CompletionCandidate::Declaration(id) => {
713
+ let decl = graph
714
+ .declarations()
715
+ .get(&id)
716
+ .expect("completion candidate declaration must exist in graph");
717
+ CCompletionCandidate {
718
+ kind: CCompletionCandidateKind::Declaration,
719
+ declaration: Box::into_raw(Box::new(CDeclaration::from_declaration(id, decl))),
720
+ name: ptr::null(),
721
+ documentation: ptr::null(),
722
+ }
723
+ }
724
+ CompletionCandidate::Keyword(kw) => CCompletionCandidate {
725
+ kind: CCompletionCandidateKind::Keyword,
726
+ declaration: ptr::null(),
727
+ name: CString::new(kw.name())
728
+ .expect("keyword name must not contain NUL")
729
+ .into_raw()
730
+ .cast_const(),
731
+ documentation: CString::new(kw.documentation())
732
+ .expect("keyword documentation must not contain NUL")
733
+ .into_raw()
734
+ .cast_const(),
735
+ },
736
+ CompletionCandidate::KeywordArgument(str_id) => {
737
+ let name_str = graph
738
+ .strings()
739
+ .get(&str_id)
740
+ .expect("keyword argument string must exist in graph");
741
+ CCompletionCandidate {
742
+ kind: CCompletionCandidateKind::KeywordParameter,
743
+ declaration: ptr::null(),
744
+ name: CString::new(name_str.as_str())
745
+ .expect("keyword argument name must not contain NUL")
746
+ .into_raw()
747
+ .cast_const(),
748
+ documentation: ptr::null(),
749
+ }
750
+ }
751
+ })
752
+ .collect();
753
+
754
+ for name_id in names_to_untrack {
755
+ graph.untrack_name(name_id);
756
+ }
757
+
758
+ CompletionResult::success(CompletionCandidateArray::from_vec(entries))
759
+ }
760
+
761
+ /// Returns expression completion candidates.
762
+ /// The caller must free candidates with `rdx_completion_candidates_free`
763
+ /// and the error string (if non-null) with `free_c_string`.
764
+ ///
765
+ /// # Safety
766
+ ///
767
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
768
+ /// - `nesting` must point to `nesting_count` valid, null-terminated UTF-8 strings.
769
+ #[unsafe(no_mangle)]
770
+ pub unsafe extern "C" fn rdx_graph_complete_expression(
771
+ pointer: GraphPointer,
772
+ nesting: *const *const c_char,
773
+ nesting_count: usize,
774
+ ) -> CompletionResult {
775
+ with_mut_graph(pointer, |graph| {
776
+ let Ok((name_id, names_to_untrack)) = (unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
777
+ else {
778
+ return CompletionResult::success(ptr::null_mut());
779
+ };
780
+
781
+ run_and_finalize_completion(graph, CompletionReceiver::Expression(name_id), names_to_untrack)
782
+ })
783
+ }
784
+
785
+ /// Returns namespace access completion candidates.
786
+ /// The caller must free candidates with `rdx_completion_candidates_free`
787
+ /// and the error string (if non-null) with `free_c_string`.
788
+ ///
789
+ /// # Safety
790
+ ///
791
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
792
+ /// - `name` must be a valid, null-terminated UTF-8 string (FQN of the namespace).
793
+ #[unsafe(no_mangle)]
794
+ pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
795
+ pointer: GraphPointer,
796
+ name: *const c_char,
797
+ ) -> CompletionResult {
798
+ let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
799
+ return CompletionResult::success(ptr::null_mut());
800
+ };
801
+
802
+ with_mut_graph(pointer, |graph| {
803
+ run_and_finalize_completion(
804
+ graph,
805
+ CompletionReceiver::NamespaceAccess(DeclarationId::from(name_str.as_str())),
806
+ Vec::new(),
807
+ )
808
+ })
809
+ }
810
+
811
+ /// Returns method call completion candidates.
812
+ /// The caller must free candidates with `rdx_completion_candidates_free`
813
+ /// and the error string (if non-null) with `free_c_string`.
814
+ ///
815
+ /// # Safety
816
+ ///
817
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
818
+ /// - `name` must be a valid, null-terminated UTF-8 string (FQN of the receiver).
819
+ #[unsafe(no_mangle)]
820
+ pub unsafe extern "C" fn rdx_graph_complete_method_call(
821
+ pointer: GraphPointer,
822
+ name: *const c_char,
823
+ ) -> CompletionResult {
824
+ let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
825
+ return CompletionResult::success(ptr::null_mut());
826
+ };
827
+
828
+ with_mut_graph(pointer, |graph| {
829
+ run_and_finalize_completion(
830
+ graph,
831
+ CompletionReceiver::MethodCall(DeclarationId::from(name_str.as_str())),
832
+ Vec::new(),
833
+ )
834
+ })
835
+ }
836
+
837
+ /// Returns method argument completion candidates.
838
+ /// The caller must free candidates with `rdx_completion_candidates_free`
839
+ /// and the error string (if non-null) with `free_c_string`.
840
+ ///
841
+ /// # Safety
842
+ ///
843
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
844
+ /// - `name` must be a valid, null-terminated UTF-8 string (FQN of the method).
845
+ /// - `nesting` must point to `nesting_count` valid, null-terminated UTF-8 strings.
846
+ #[unsafe(no_mangle)]
847
+ pub unsafe extern "C" fn rdx_graph_complete_method_argument(
848
+ pointer: GraphPointer,
849
+ name: *const c_char,
850
+ nesting: *const *const c_char,
851
+ nesting_count: usize,
852
+ ) -> CompletionResult {
853
+ let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
854
+ return CompletionResult::success(ptr::null_mut());
855
+ };
856
+
857
+ with_mut_graph(pointer, |graph| {
858
+ let Ok((self_name_id, names_to_untrack)) =
859
+ (unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
860
+ else {
861
+ return CompletionResult::success(ptr::null_mut());
862
+ };
863
+
864
+ run_and_finalize_completion(
865
+ graph,
866
+ CompletionReceiver::MethodArgument {
867
+ self_name_id,
868
+ method_decl_id: DeclarationId::from(name_str.as_str()),
869
+ },
870
+ names_to_untrack,
871
+ )
872
+ })
873
+ }
874
+
875
+ #[cfg(test)]
876
+ mod tests {
877
+ use rubydex::indexing::ruby_indexer::RubyIndexer;
878
+
879
+ use super::*;
880
+
881
+ #[test]
882
+ fn names_are_untracked_after_resolving_constant() {
883
+ let mut indexer = RubyIndexer::new(
884
+ "file:///foo.rb".into(),
885
+ "
886
+ class Foo
887
+ BAR = 1
888
+ end
889
+ ",
890
+ );
891
+ indexer.index();
892
+
893
+ let mut graph = Graph::new();
894
+ graph.consume_document_changes(indexer.local_graph());
895
+ let mut resolver = Resolver::new(&mut graph);
896
+ resolver.resolve();
897
+
898
+ assert_eq!(
899
+ 1,
900
+ graph
901
+ .names()
902
+ .iter()
903
+ .find_map(|(_, name)| {
904
+ if graph.strings().get(name.str()).unwrap().as_str() == "BAR" {
905
+ Some(name)
906
+ } else {
907
+ None
908
+ }
909
+ })
910
+ .unwrap()
911
+ .ref_count()
912
+ );
913
+
914
+ let graph_ptr = Box::into_raw(Box::new(graph)) as GraphPointer;
915
+
916
+ // Build the nesting array: ["Foo"] since BAR is inside class Foo
917
+ let nesting_strings = [CString::new("Foo").unwrap()];
918
+ let nesting_ptrs: Vec<*const c_char> = nesting_strings.iter().map(|s| s.as_ptr()).collect();
919
+
920
+ unsafe {
921
+ let decl = rdx_graph_resolve_constant(
922
+ graph_ptr,
923
+ CString::new("BAR").unwrap().as_ptr(),
924
+ nesting_ptrs.as_ptr(),
925
+ nesting_ptrs.len(),
926
+ );
927
+ assert_eq!((*decl).id(), *DeclarationId::from("Foo::BAR"));
928
+ };
929
+
930
+ let graph = unsafe { Box::from_raw(graph_ptr.cast::<Graph>()) };
931
+
932
+ assert_eq!(
933
+ 1,
934
+ graph
935
+ .names()
936
+ .iter()
937
+ .find_map(|(_, name)| {
938
+ if graph.strings().get(name.str()).unwrap().as_str() == "BAR" {
939
+ Some(name)
940
+ } else {
941
+ None
942
+ }
943
+ })
944
+ .unwrap()
945
+ .ref_count()
946
+ );
947
+ }
948
+ }