Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ext/rubydex/definition.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "location.h"
#include "ruby/internal/scan_args.h"
#include "rustbindings.h"
#include "signature.h"

static VALUE mRubydex;
VALUE cComment;
Expand Down Expand Up @@ -164,6 +165,18 @@ static VALUE rdxr_definition_name_location(VALUE self) {
return location;
}

// MethodDefinition#signatures -> [Rubydex::Signature]
static VALUE rdxr_method_definition_signatures(VALUE self) {
HandleData *data;
TypedData_Get_Struct(self, HandleData, &handle_type, data);

void *graph;
TypedData_Get_Struct(data->graph_obj, void *, &graph_type, graph);

SignatureArray *arr = rdx_definition_signatures(graph, data->id);
return rdxi_signatures_to_ruby(arr, data->graph_obj, self);
}

void rdxi_initialize_definition(VALUE mod) {
mRubydex = mod;

Expand All @@ -186,6 +199,7 @@ void rdxi_initialize_definition(VALUE mod) {
cConstantAliasDefinition = rb_define_class_under(mRubydex, "ConstantAliasDefinition", cDefinition);
cConstantVisibilityDefinition = rb_define_class_under(mRubydex, "ConstantVisibilityDefinition", cDefinition);
cMethodDefinition = rb_define_class_under(mRubydex, "MethodDefinition", cDefinition);
rb_define_method(cMethodDefinition, "signatures", rdxr_method_definition_signatures, 0);
cAttrAccessorDefinition = rb_define_class_under(mRubydex, "AttrAccessorDefinition", cDefinition);
cAttrReaderDefinition = rb_define_class_under(mRubydex, "AttrReaderDefinition", cDefinition);
cAttrWriterDefinition = rb_define_class_under(mRubydex, "AttrWriterDefinition", cDefinition);
Expand Down
2 changes: 2 additions & 0 deletions ext/rubydex/rubydex.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "graph.h"
#include "location.h"
#include "reference.h"
#include "signature.h"

VALUE mRubydex;

Expand All @@ -19,4 +20,5 @@ void Init_rubydex(void) {
rdxi_initialize_location(mRubydex);
rdxi_initialize_diagnostic(mRubydex);
rdxi_initialize_reference(mRubydex);
rdxi_initialize_signature(mRubydex);
}
87 changes: 87 additions & 0 deletions ext/rubydex/signature.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "signature.h"
#include "definition.h"
#include "handle.h"
#include "location.h"

VALUE cSignature;
VALUE cParameter;
VALUE cPositionalParameter;
VALUE cOptionalPositionalParameter;
VALUE cRestPositionalParameter;
VALUE cKeywordParameter;
VALUE cOptionalKeywordParameter;
VALUE cRestKeywordParameter;
VALUE cBlockParameter;
VALUE cForwardParameter;

static VALUE parameter_class_for_kind(ParameterKind kind) {
switch (kind) {
case ParameterKind_RequiredPositional: return cPositionalParameter;
case ParameterKind_OptionalPositional: return cOptionalPositionalParameter;
case ParameterKind_Rest: return cRestPositionalParameter;
case ParameterKind_RequiredKeyword: return cKeywordParameter;
case ParameterKind_OptionalKeyword: return cOptionalKeywordParameter;
case ParameterKind_RestKeyword: return cRestKeywordParameter;
case ParameterKind_Block: return cBlockParameter;
case ParameterKind_Forward: return cForwardParameter;
default: rb_raise(rb_eRuntimeError, "Unknown ParameterKind: %d", kind);
}
}

VALUE rdxi_signatures_to_ruby(SignatureArray *arr, VALUE graph_obj, VALUE default_method_def) {
if (arr == NULL || arr->len == 0) {
if (arr != NULL) {
rdx_definition_signatures_free(arr);
}
return rb_ary_new();
}

VALUE signatures = rb_ary_new_capa((long)arr->len);

for (size_t i = 0; i < arr->len; i++) {
SignatureEntry sig_entry = arr->items[i];

VALUE method_def;
if (default_method_def != Qnil) {
method_def = default_method_def;
} else {
VALUE def_argv[] = {graph_obj, ULL2NUM(sig_entry.definition_id)};
method_def = rb_class_new_instance(2, def_argv, cMethodDefinition);
}

VALUE parameters = rb_ary_new_capa((long)sig_entry.parameters_len);
for (size_t j = 0; j < sig_entry.parameters_len; j++) {
ParameterEntry param_entry = sig_entry.parameters[j];

VALUE param_class = parameter_class_for_kind(param_entry.kind);
VALUE name_sym = ID2SYM(rb_intern(param_entry.name));
VALUE location = rdxi_build_location_value(param_entry.location);
VALUE param_argv[] = {name_sym, location};
VALUE param = rb_class_new_instance(2, param_argv, param_class);

rb_ary_push(parameters, param);
}

VALUE sig_argv[] = {parameters, method_def};
VALUE signature = rb_class_new_instance(2, sig_argv, cSignature);

rb_ary_push(signatures, signature);
}

rdx_definition_signatures_free(arr);
return signatures;
}

void rdxi_initialize_signature(VALUE mRubydex) {
cSignature = rb_define_class_under(mRubydex, "Signature", rb_cObject);

cParameter = rb_define_class_under(cSignature, "Parameter", rb_cObject);
cPositionalParameter = rb_define_class_under(cSignature, "PositionalParameter", cParameter);
cOptionalPositionalParameter = rb_define_class_under(cSignature, "OptionalPositionalParameter", cParameter);
cRestPositionalParameter = rb_define_class_under(cSignature, "RestPositionalParameter", cParameter);
cKeywordParameter = rb_define_class_under(cSignature, "KeywordParameter", cParameter);
cOptionalKeywordParameter = rb_define_class_under(cSignature, "OptionalKeywordParameter", cParameter);
cRestKeywordParameter = rb_define_class_under(cSignature, "RestKeywordParameter", cParameter);
cBlockParameter = rb_define_class_under(cSignature, "BlockParameter", cParameter);
cForwardParameter = rb_define_class_under(cSignature, "ForwardParameter", cParameter);
Comment on lines +78 to +86
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we define these in Ruby? It seems like we may not even need new C files. We could just put the rdxi_signatures_to_ruby helper in definition.c.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also for MethodDeclaration#signatures. That will be in declaration.c and then the rdxi_signatures_to_ruby will be used from two files.

Another question is if Rubydex::Signature is a good place to define the class. Does Rubydex::MethodDefinition::Signature make more sense?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far, we haven't really nested any of the classes in sub-modules. I think Rubydex::Signature is fine for now.

}
26 changes: 26 additions & 0 deletions ext/rubydex/signature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef RUBYDEX_SIGNATURE_H
#define RUBYDEX_SIGNATURE_H

#include "ruby.h"
#include "rustbindings.h"

extern VALUE cSignature;
extern VALUE cParameter;
extern VALUE cPositionalParameter;
extern VALUE cOptionalPositionalParameter;
extern VALUE cRestPositionalParameter;
extern VALUE cKeywordParameter;
extern VALUE cOptionalKeywordParameter;
extern VALUE cRestKeywordParameter;
extern VALUE cBlockParameter;
extern VALUE cForwardParameter;

// Convert a SignatureArray into a Ruby array of Rubydex::Signature objects.
// If default_method_def is not Qnil, it is used as method_definition for all signatures.
// Otherwise, a new MethodDefinition handle is built from each SignatureEntry's definition_id.
// The SignatureArray is freed after conversion.
VALUE rdxi_signatures_to_ruby(SignatureArray *arr, VALUE graph_obj, VALUE default_method_def);

void rdxi_initialize_signature(VALUE mRubydex);

#endif // RUBYDEX_SIGNATURE_H
1 change: 1 addition & 0 deletions lib/rubydex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
require "rubydex/failures"
require "rubydex/location"
require "rubydex/comment"
require "rubydex/signature"
require "rubydex/diagnostic"
require "rubydex/graph"
31 changes: 31 additions & 0 deletions lib/rubydex/signature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Rubydex
class Signature
class Parameter
#: Symbol
attr_reader :name

#: Location
attr_reader :location

#: (Symbol, Location) -> void
def initialize(name, location)
@name = name
@location = location
end
end

#: Array[Parameter]
attr_reader :parameters

#: MethodDefinition
attr_reader :method_definition

#: (Array[Parameter], MethodDefinition) -> void
def initialize(parameters, method_definition)
@parameters = parameters
@method_definition = method_definition
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this back link? Considering that consumers will interact with the API like this:

declaration.definitions.first.signatures

I think we can remove this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for MethodDeclaration#signatures. It will return an array of Signature, and then we should need the backlink to retrieve docs and something associated to the method definition.

We can drop the backlink and add a new class or use tuple for #signatures method though?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss in the team sync, but I think exposing signatures at the declaration level has the potential to be a bit confusing.

Maybe we can handle the alias case in some other way.

end
end
end
165 changes: 164 additions & 1 deletion rust/rubydex-sys/src/definition_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::graph_api::{GraphPointer, with_graph};
use crate::location_api::{Location, create_location_for_uri_and_offset};
use libc::c_char;
use rubydex::model::definitions::Definition;
use rubydex::model::definitions::{Definition, Parameter};
use rubydex::model::ids::DefinitionId;
use std::ffi::CString;
use std::ptr;
Expand Down Expand Up @@ -350,3 +350,166 @@ pub unsafe extern "C" fn rdx_definition_name_location(pointer: GraphPointer, def
create_location_for_uri_and_offset(graph, document, name_offset)
})
}

/// C-compatible enum representing the kind of a parameter.
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ParameterKind {
RequiredPositional = 0,
OptionalPositional = 1,
Rest = 2,
RequiredKeyword = 3,
OptionalKeyword = 4,
RestKeyword = 5,
Block = 6,
Forward = 7,
}

fn map_parameter_kind(param: &Parameter) -> ParameterKind {
match param {
Parameter::RequiredPositional(_) | Parameter::Post(_) => ParameterKind::RequiredPositional,
Parameter::OptionalPositional(_) => ParameterKind::OptionalPositional,
Parameter::RestPositional(_) => ParameterKind::Rest,
Parameter::RequiredKeyword(_) => ParameterKind::RequiredKeyword,
Parameter::OptionalKeyword(_) => ParameterKind::OptionalKeyword,
Parameter::RestKeyword(_) => ParameterKind::RestKeyword,
Parameter::Block(_) => ParameterKind::Block,
Parameter::Forward(_) => ParameterKind::Forward,
}
}

/// C-compatible struct representing a single parameter with its name, kind, and location.
#[repr(C)]
pub struct ParameterEntry {
pub name: *const c_char,
pub kind: ParameterKind,
pub location: *mut Location,
}

/// C-compatible struct representing a single method signature (a list of parameters).
#[repr(C)]
pub struct SignatureEntry {
pub definition_id: u64,
pub parameters: *mut ParameterEntry,
pub parameters_len: usize,
}

/// C-compatible array of signatures.
#[repr(C)]
pub struct SignatureArray {
pub items: *mut SignatureEntry,
pub len: usize,
}

/// Returns a newly allocated array of signatures for the given method definition id.
/// Returns NULL if the definition is not a method definition.
/// Caller must free the returned pointer with `rdx_definition_signatures_free`.
///
/// # Safety
/// - `pointer` must be a valid pointer previously returned by `rdx_graph_new`.
/// - `definition_id` must be a valid definition id.
///
/// # Panics
/// This function will panic if a definition or document cannot be found.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rdx_definition_signatures(pointer: GraphPointer, definition_id: u64) -> *mut SignatureArray {
with_graph(pointer, |graph| {
let def_id = DefinitionId::new(definition_id);
let Some(Definition::Method(method_def)) = graph.definitions().get(&def_id) else {
return ptr::null_mut();
};

let mut sig_entries: Vec<SignatureEntry> = Vec::new();
collect_method_signatures(graph, method_def, definition_id, &mut sig_entries);

let mut boxed = sig_entries.into_boxed_slice();
let len = boxed.len();
let items_ptr = boxed.as_mut_ptr();
std::mem::forget(boxed);

Box::into_raw(Box::new(SignatureArray { items: items_ptr, len }))
})
}

/// Helper: build signature entries from a `MethodDefinition` and append them to the output vector.
fn collect_method_signatures(
graph: &rubydex::model::graph::Graph,
method_def: &rubydex::model::definitions::MethodDefinition,
definition_id: u64,
out: &mut Vec<SignatureEntry>,
) {
let uri_id = *method_def.uri_id();
let document = graph.documents().get(&uri_id).expect("document should exist");

for sig in method_def.signatures().as_slice() {
let mut param_entries = sig
.iter()
.map(|param| {
let param_struct = param.inner();
let name = graph
.strings()
.get(param_struct.str())
.expect("parameter name string should exist");
let name_str = CString::new(name.as_str()).unwrap().into_raw().cast_const();

ParameterEntry {
name: name_str,
kind: map_parameter_kind(param),
location: create_location_for_uri_and_offset(graph, document, param_struct.offset()),
}
})
.collect::<Vec<_>>()
.into_boxed_slice();

let parameters_len = param_entries.len();
let parameters_ptr = param_entries.as_mut_ptr();
std::mem::forget(param_entries);

out.push(SignatureEntry {
definition_id,
parameters: parameters_ptr,
parameters_len,
});
}
}

/// Frees a `SignatureArray` previously returned by `rdx_definition_signatures`.
///
/// # Safety
/// - `ptr` must be a valid pointer previously returned by `rdx_definition_signatures`.
/// - `ptr` must not be used after being freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rdx_definition_signatures_free(ptr: *mut SignatureArray) {
if ptr.is_null() {
return;
}

// Take ownership of the SignatureArray
let arr = unsafe { Box::from_raw(ptr) };

if !arr.items.is_null() && arr.len > 0 {
// Reconstruct the boxed slice so we can drop it after freeing inner allocations
let slice_ptr = ptr::slice_from_raw_parts_mut(arr.items, arr.len);
let mut sig_slice: Box<[SignatureEntry]> = unsafe { Box::from_raw(slice_ptr) };

for sig_entry in &mut sig_slice {
if !sig_entry.parameters.is_null() && sig_entry.parameters_len > 0 {
let param_slice_ptr = ptr::slice_from_raw_parts_mut(sig_entry.parameters, sig_entry.parameters_len);
let mut param_slice: Box<[ParameterEntry]> = unsafe { Box::from_raw(param_slice_ptr) };

for param_entry in &mut param_slice {
if !param_entry.name.is_null() {
let _ = unsafe { CString::from_raw(param_entry.name.cast_mut()) };
}
if !param_entry.location.is_null() {
unsafe { crate::location_api::rdx_location_free(param_entry.location) };
param_entry.location = ptr::null_mut();
}
}
// param_slice is dropped here, freeing the parameters buffer
}
}
// sig_slice is dropped here, freeing the signatures buffer
}
// arr is dropped here
}
Loading
Loading