|
3 | 3 | use crate::graph_api::{GraphPointer, with_graph}; |
4 | 4 | use crate::location_api::{Location, create_location_for_uri_and_offset}; |
5 | 5 | use libc::c_char; |
6 | | -use rubydex::model::definitions::Definition; |
| 6 | +use rubydex::model::definitions::{Definition, Parameter}; |
7 | 7 | use rubydex::model::ids::DefinitionId; |
8 | 8 | use std::ffi::CString; |
9 | 9 | use std::ptr; |
@@ -350,3 +350,170 @@ pub unsafe extern "C" fn rdx_definition_name_location(pointer: GraphPointer, def |
350 | 350 | create_location_for_uri_and_offset(graph, document, name_offset) |
351 | 351 | }) |
352 | 352 | } |
| 353 | + |
| 354 | +/// C-compatible enum representing the kind of a parameter. |
| 355 | +#[repr(C)] |
| 356 | +#[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| 357 | +pub enum ParameterKind { |
| 358 | + Req = 0, |
| 359 | + Opt = 1, |
| 360 | + Rest = 2, |
| 361 | + Keyreq = 3, |
| 362 | + Key = 4, |
| 363 | + Keyrest = 5, |
| 364 | + Block = 6, |
| 365 | + Forward = 7, |
| 366 | +} |
| 367 | + |
| 368 | +fn map_parameter_kind(param: &Parameter) -> ParameterKind { |
| 369 | + match param { |
| 370 | + Parameter::RequiredPositional(_) | Parameter::Post(_) => ParameterKind::Req, |
| 371 | + Parameter::OptionalPositional(_) => ParameterKind::Opt, |
| 372 | + Parameter::RestPositional(_) => ParameterKind::Rest, |
| 373 | + Parameter::RequiredKeyword(_) => ParameterKind::Keyreq, |
| 374 | + Parameter::OptionalKeyword(_) => ParameterKind::Key, |
| 375 | + Parameter::RestKeyword(_) => ParameterKind::Keyrest, |
| 376 | + Parameter::Block(_) => ParameterKind::Block, |
| 377 | + Parameter::Forward(_) => ParameterKind::Forward, |
| 378 | + } |
| 379 | +} |
| 380 | + |
| 381 | +/// C-compatible struct representing a single parameter with its name, kind, and location. |
| 382 | +#[repr(C)] |
| 383 | +pub struct ParameterEntry { |
| 384 | + pub name: *const c_char, |
| 385 | + pub kind: ParameterKind, |
| 386 | + pub location: *mut Location, |
| 387 | +} |
| 388 | + |
| 389 | +/// C-compatible struct representing a single method signature (a list of parameters). |
| 390 | +#[repr(C)] |
| 391 | +pub struct SignatureEntry { |
| 392 | + pub definition_id: u64, |
| 393 | + pub parameters: *mut ParameterEntry, |
| 394 | + pub parameters_len: usize, |
| 395 | +} |
| 396 | + |
| 397 | +/// C-compatible array of signatures. |
| 398 | +#[repr(C)] |
| 399 | +pub struct SignatureArray { |
| 400 | + pub items: *mut SignatureEntry, |
| 401 | + pub len: usize, |
| 402 | +} |
| 403 | + |
| 404 | +/// Returns a newly allocated array of signatures for the given method definition id. |
| 405 | +/// Returns NULL if the definition is not a method definition. |
| 406 | +/// Caller must free the returned pointer with `rdx_definition_signatures_free`. |
| 407 | +/// |
| 408 | +/// # Safety |
| 409 | +/// - `pointer` must be a valid pointer previously returned by `rdx_graph_new`. |
| 410 | +/// - `definition_id` must be a valid definition id. |
| 411 | +/// |
| 412 | +/// # Panics |
| 413 | +/// This function will panic if a definition or document cannot be found. |
| 414 | +#[unsafe(no_mangle)] |
| 415 | +pub unsafe extern "C" fn rdx_definition_signatures(pointer: GraphPointer, definition_id: u64) -> *mut SignatureArray { |
| 416 | + with_graph(pointer, |graph| { |
| 417 | + let def_id = DefinitionId::new(definition_id); |
| 418 | + let Some(defn) = graph.definitions().get(&def_id) else { |
| 419 | + panic!("Definition not found: {definition_id:?}"); |
| 420 | + }; |
| 421 | + |
| 422 | + let Definition::Method(method_def) = defn else { |
| 423 | + return ptr::null_mut(); |
| 424 | + }; |
| 425 | + |
| 426 | + let mut sig_entries: Vec<SignatureEntry> = Vec::new(); |
| 427 | + collect_method_signatures(graph, method_def, definition_id, &mut sig_entries); |
| 428 | + |
| 429 | + let mut boxed = sig_entries.into_boxed_slice(); |
| 430 | + let len = boxed.len(); |
| 431 | + let items_ptr = boxed.as_mut_ptr(); |
| 432 | + std::mem::forget(boxed); |
| 433 | + |
| 434 | + Box::into_raw(Box::new(SignatureArray { items: items_ptr, len })) |
| 435 | + }) |
| 436 | +} |
| 437 | + |
| 438 | +/// Helper: build signature entries from a MethodDefinition and append them to the output vector. |
| 439 | +pub(crate) fn collect_method_signatures( |
| 440 | + graph: &rubydex::model::graph::Graph, |
| 441 | + method_def: &rubydex::model::definitions::MethodDefinition, |
| 442 | + definition_id: u64, |
| 443 | + out: &mut Vec<SignatureEntry>, |
| 444 | +) { |
| 445 | + let uri_id = *method_def.uri_id(); |
| 446 | + let document = graph.documents().get(&uri_id).expect("document should exist"); |
| 447 | + |
| 448 | + for sig in method_def.signatures().as_slice() { |
| 449 | + let mut param_entries = sig |
| 450 | + .iter() |
| 451 | + .map(|param| { |
| 452 | + let param_struct = param.inner(); |
| 453 | + let name = graph |
| 454 | + .strings() |
| 455 | + .get(param_struct.str()) |
| 456 | + .expect("parameter name string should exist"); |
| 457 | + let name_str = CString::new(name.as_str()).unwrap().into_raw().cast_const(); |
| 458 | + |
| 459 | + ParameterEntry { |
| 460 | + name: name_str, |
| 461 | + kind: map_parameter_kind(param), |
| 462 | + location: create_location_for_uri_and_offset(graph, document, param_struct.offset()), |
| 463 | + } |
| 464 | + }) |
| 465 | + .collect::<Vec<_>>() |
| 466 | + .into_boxed_slice(); |
| 467 | + |
| 468 | + let parameters_len = param_entries.len(); |
| 469 | + let parameters_ptr = param_entries.as_mut_ptr(); |
| 470 | + std::mem::forget(param_entries); |
| 471 | + |
| 472 | + out.push(SignatureEntry { |
| 473 | + definition_id, |
| 474 | + parameters: parameters_ptr, |
| 475 | + parameters_len, |
| 476 | + }); |
| 477 | + } |
| 478 | +} |
| 479 | + |
| 480 | +/// Frees a `SignatureArray` previously returned by `rdx_definition_signatures`. |
| 481 | +/// |
| 482 | +/// # Safety |
| 483 | +/// - `ptr` must be a valid pointer previously returned by `rdx_definition_signatures`. |
| 484 | +/// - `ptr` must not be used after being freed. |
| 485 | +#[unsafe(no_mangle)] |
| 486 | +pub unsafe extern "C" fn rdx_definition_signatures_free(ptr: *mut SignatureArray) { |
| 487 | + if ptr.is_null() { |
| 488 | + return; |
| 489 | + } |
| 490 | + |
| 491 | + // Take ownership of the SignatureArray |
| 492 | + let arr = unsafe { Box::from_raw(ptr) }; |
| 493 | + |
| 494 | + if !arr.items.is_null() && arr.len > 0 { |
| 495 | + // Reconstruct the boxed slice so we can drop it after freeing inner allocations |
| 496 | + let slice_ptr = ptr::slice_from_raw_parts_mut(arr.items, arr.len); |
| 497 | + let mut sig_slice: Box<[SignatureEntry]> = unsafe { Box::from_raw(slice_ptr) }; |
| 498 | + |
| 499 | + for sig_entry in &mut sig_slice { |
| 500 | + if !sig_entry.parameters.is_null() && sig_entry.parameters_len > 0 { |
| 501 | + let param_slice_ptr = ptr::slice_from_raw_parts_mut(sig_entry.parameters, sig_entry.parameters_len); |
| 502 | + let mut param_slice: Box<[ParameterEntry]> = unsafe { Box::from_raw(param_slice_ptr) }; |
| 503 | + |
| 504 | + for param_entry in &mut param_slice { |
| 505 | + if !param_entry.name.is_null() { |
| 506 | + let _ = unsafe { CString::from_raw(param_entry.name.cast_mut()) }; |
| 507 | + } |
| 508 | + if !param_entry.location.is_null() { |
| 509 | + unsafe { crate::location_api::rdx_location_free(param_entry.location) }; |
| 510 | + param_entry.location = ptr::null_mut(); |
| 511 | + } |
| 512 | + } |
| 513 | + // param_slice is dropped here, freeing the parameters buffer |
| 514 | + } |
| 515 | + } |
| 516 | + // sig_slice is dropped here, freeing the signatures buffer |
| 517 | + } |
| 518 | + // arr is dropped here |
| 519 | +} |
0 commit comments