rubydex 0.1.0.beta11 → 0.1.0.beta13

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