Skip to content

Commit 253d8fd

Browse files
soutaroclaude
andcommitted
Add MethodDefinition#signatures to Ruby API
Expose method signature information through the Ruby C extension. Each signature contains parameters as an array of Signature::Parameter subclasses (PositionalParameter, KeywordParameter, etc.), each holding a name and location. - Add Parameter::inner() to access ParameterStruct from any variant - Add Rust C API: ParameterKind, SignatureEntry, SignatureArray structs and rdx_definition_signatures/rdx_definition_signatures_free functions - Add Rubydex::Signature Ruby class with #parameters and #method_definition - Add Rubydex::Signature::Parameter base class and typed subclasses - Add rdxi_signatures_to_ruby shared C helper for SignatureArray conversion - Register MethodDefinition#signatures in the C extension Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3dba913 commit 253d8fd

9 files changed

Lines changed: 557 additions & 1 deletion

File tree

ext/rubydex/definition.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "location.h"
55
#include "ruby/internal/scan_args.h"
66
#include "rustbindings.h"
7+
#include "signature.h"
78

89
static VALUE mRubydex;
910
VALUE cComment;
@@ -164,6 +165,18 @@ static VALUE rdxr_definition_name_location(VALUE self) {
164165
return location;
165166
}
166167

168+
// MethodDefinition#signatures -> [Rubydex::Signature]
169+
static VALUE rdxr_method_definition_signatures(VALUE self) {
170+
HandleData *data;
171+
TypedData_Get_Struct(self, HandleData, &handle_type, data);
172+
173+
void *graph;
174+
TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);
175+
176+
SignatureArray *arr = rdx_definition_signatures(graph, data->id);
177+
return rdxi_signatures_to_ruby(arr, data->graph_obj, self);
178+
}
179+
167180
void rdxi_initialize_definition(VALUE mod) {
168181
mRubydex = mod;
169182

@@ -186,6 +199,7 @@ void rdxi_initialize_definition(VALUE mod) {
186199
cConstantAliasDefinition = rb_define_class_under(mRubydex, "ConstantAliasDefinition", cDefinition);
187200
cConstantVisibilityDefinition = rb_define_class_under(mRubydex, "ConstantVisibilityDefinition", cDefinition);
188201
cMethodDefinition = rb_define_class_under(mRubydex, "MethodDefinition", cDefinition);
202+
rb_define_method(cMethodDefinition, "signatures", rdxr_method_definition_signatures, 0);
189203
cAttrAccessorDefinition = rb_define_class_under(mRubydex, "AttrAccessorDefinition", cDefinition);
190204
cAttrReaderDefinition = rb_define_class_under(mRubydex, "AttrReaderDefinition", cDefinition);
191205
cAttrWriterDefinition = rb_define_class_under(mRubydex, "AttrWriterDefinition", cDefinition);

ext/rubydex/rubydex.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "graph.h"
66
#include "location.h"
77
#include "reference.h"
8+
#include "signature.h"
89

910
VALUE mRubydex;
1011

@@ -19,4 +20,5 @@ void Init_rubydex(void) {
1920
rdxi_initialize_location(mRubydex);
2021
rdxi_initialize_diagnostic(mRubydex);
2122
rdxi_initialize_reference(mRubydex);
23+
rdxi_initialize_signature(mRubydex);
2224
}

ext/rubydex/signature.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#include "signature.h"
2+
#include "definition.h"
3+
#include "handle.h"
4+
#include "location.h"
5+
6+
VALUE cSignature;
7+
VALUE cParameter;
8+
VALUE cPositionalParameter;
9+
VALUE cOptionalPositionalParameter;
10+
VALUE cRestPositionalParameter;
11+
VALUE cKeywordParameter;
12+
VALUE cOptionalKeywordParameter;
13+
VALUE cRestKeywordParameter;
14+
VALUE cBlockParameter;
15+
VALUE cForwardParameter;
16+
17+
static VALUE parameter_class_for_kind(ParameterKind kind) {
18+
switch (kind) {
19+
case ParameterKind_Req: return cPositionalParameter;
20+
case ParameterKind_Opt: return cOptionalPositionalParameter;
21+
case ParameterKind_Rest: return cRestPositionalParameter;
22+
case ParameterKind_Keyreq: return cKeywordParameter;
23+
case ParameterKind_Key: return cOptionalKeywordParameter;
24+
case ParameterKind_Keyrest: return cRestKeywordParameter;
25+
case ParameterKind_Block: return cBlockParameter;
26+
case ParameterKind_Forward: return cForwardParameter;
27+
default: rb_raise(rb_eRuntimeError, "Unknown ParameterKind: %d", kind);
28+
}
29+
}
30+
31+
VALUE rdxi_signatures_to_ruby(SignatureArray *arr, VALUE graph_obj, VALUE default_method_def) {
32+
if (arr == NULL || arr->len == 0) {
33+
if (arr != NULL) {
34+
rdx_definition_signatures_free(arr);
35+
}
36+
return rb_ary_new();
37+
}
38+
39+
VALUE signatures = rb_ary_new_capa((long)arr->len);
40+
41+
for (size_t i = 0; i < arr->len; i++) {
42+
SignatureEntry sig_entry = arr->items[i];
43+
44+
VALUE method_def;
45+
if (default_method_def != Qnil) {
46+
method_def = default_method_def;
47+
} else {
48+
VALUE def_argv[] = {graph_obj, ULL2NUM(sig_entry.definition_id)};
49+
method_def = rb_class_new_instance(2, def_argv, cMethodDefinition);
50+
}
51+
52+
VALUE parameters = rb_ary_new_capa((long)sig_entry.parameters_len);
53+
for (size_t j = 0; j < sig_entry.parameters_len; j++) {
54+
ParameterEntry param_entry = sig_entry.parameters[j];
55+
56+
VALUE param_class = parameter_class_for_kind(param_entry.kind);
57+
VALUE name_sym = ID2SYM(rb_intern(param_entry.name));
58+
VALUE location = rdxi_build_location_value(param_entry.location);
59+
VALUE param_argv[] = {name_sym, location};
60+
VALUE param = rb_class_new_instance(2, param_argv, param_class);
61+
62+
rb_ary_push(parameters, param);
63+
}
64+
65+
VALUE sig_kwargs = rb_hash_new();
66+
rb_hash_aset(sig_kwargs, ID2SYM(rb_intern("parameters")), parameters);
67+
rb_hash_aset(sig_kwargs, ID2SYM(rb_intern("method_definition")), method_def);
68+
VALUE signature = rb_class_new_instance_kw(1, &sig_kwargs, cSignature, RB_PASS_KEYWORDS);
69+
70+
rb_ary_push(signatures, signature);
71+
}
72+
73+
rdx_definition_signatures_free(arr);
74+
return signatures;
75+
}
76+
77+
void rdxi_initialize_signature(VALUE mRubydex) {
78+
cSignature = rb_define_class_under(mRubydex, "Signature", rb_cObject);
79+
80+
cParameter = rb_define_class_under(cSignature, "Parameter", rb_cObject);
81+
cPositionalParameter = rb_define_class_under(cSignature, "PositionalParameter", cParameter);
82+
cOptionalPositionalParameter = rb_define_class_under(cSignature, "OptionalPositionalParameter", cParameter);
83+
cRestPositionalParameter = rb_define_class_under(cSignature, "RestPositionalParameter", cParameter);
84+
cKeywordParameter = rb_define_class_under(cSignature, "KeywordParameter", cParameter);
85+
cOptionalKeywordParameter = rb_define_class_under(cSignature, "OptionalKeywordParameter", cParameter);
86+
cRestKeywordParameter = rb_define_class_under(cSignature, "RestKeywordParameter", cParameter);
87+
cBlockParameter = rb_define_class_under(cSignature, "BlockParameter", cParameter);
88+
cForwardParameter = rb_define_class_under(cSignature, "ForwardParameter", cParameter);
89+
}

ext/rubydex/signature.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef RUBYDEX_SIGNATURE_H
2+
#define RUBYDEX_SIGNATURE_H
3+
4+
#include "ruby.h"
5+
#include "rustbindings.h"
6+
7+
extern VALUE cSignature;
8+
extern VALUE cParameter;
9+
extern VALUE cPositionalParameter;
10+
extern VALUE cOptionalPositionalParameter;
11+
extern VALUE cRestPositionalParameter;
12+
extern VALUE cKeywordParameter;
13+
extern VALUE cOptionalKeywordParameter;
14+
extern VALUE cRestKeywordParameter;
15+
extern VALUE cBlockParameter;
16+
extern VALUE cForwardParameter;
17+
18+
// Convert a SignatureArray into a Ruby array of Rubydex::Signature objects.
19+
// If default_method_def is not Qnil, it is used as method_definition for all signatures.
20+
// Otherwise, a new MethodDefinition handle is built from each SignatureEntry's definition_id.
21+
// The SignatureArray is freed after conversion.
22+
VALUE rdxi_signatures_to_ruby(SignatureArray *arr, VALUE graph_obj, VALUE default_method_def);
23+
24+
void rdxi_initialize_signature(VALUE mRubydex);
25+
26+
#endif // RUBYDEX_SIGNATURE_H

lib/rubydex.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
require "rubydex/failures"
1717
require "rubydex/location"
1818
require "rubydex/comment"
19+
require "rubydex/signature"
1920
require "rubydex/diagnostic"
2021
require "rubydex/graph"

lib/rubydex/signature.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module Rubydex
4+
class Signature
5+
class Parameter
6+
#: Symbol
7+
attr_reader :name
8+
9+
#: Location
10+
attr_reader :location
11+
12+
#: (Symbol, Location) -> void
13+
def initialize(name, location)
14+
@name = name
15+
@location = location
16+
end
17+
end
18+
19+
#: Array[Parameter]
20+
attr_reader :parameters
21+
22+
#: MethodDefinition
23+
attr_reader :method_definition
24+
25+
#: (parameters: Array[Parameter], method_definition: MethodDefinition) -> void
26+
def initialize(parameters:, method_definition:)
27+
@parameters = parameters
28+
@method_definition = method_definition
29+
end
30+
end
31+
end

rust/rubydex-sys/src/definition_api.rs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::graph_api::{GraphPointer, with_graph};
44
use crate::location_api::{Location, create_location_for_uri_and_offset};
55
use libc::c_char;
6-
use rubydex::model::definitions::Definition;
6+
use rubydex::model::definitions::{Definition, Parameter};
77
use rubydex::model::ids::DefinitionId;
88
use std::ffi::CString;
99
use std::ptr;
@@ -350,3 +350,170 @@ pub unsafe extern "C" fn rdx_definition_name_location(pointer: GraphPointer, def
350350
create_location_for_uri_and_offset(graph, document, name_offset)
351351
})
352352
}
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

Comments
 (0)