Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b32b692
Add Rubydex::Signature class and Rust C API for method signatures
soutaro Mar 31, 2026
3203c85
Add MethodDefinition#signatures to Ruby API
soutaro Mar 31, 2026
8379cc7
Implement `MethodAliasDefinition#signatures`
soutaro Mar 31, 2026
d7681eb
Find inherited methods
soutaro Mar 31, 2026
a8f4c85
Delete `Signature#method_definition`
soutaro Apr 1, 2026
7d9657a
Rename parameter subclasses
soutaro Apr 1, 2026
555cbed
Extract signature_api.rs
soutaro Apr 1, 2026
d625d55
Refactor `rdx_declaration_find_member`
soutaro Apr 1, 2026
a2aedb6
Fix dealias_method
soutaro Apr 1, 2026
4066306
Add test for find_member_in_ancestors
soutaro Apr 1, 2026
783402a
Add test of deep_dealias_method
soutaro Apr 1, 2026
d8520b7
👮
soutaro Apr 1, 2026
3762ce3
lint
soutaro Apr 1, 2026
b73cb99
Create *dynamic* symbols, which are garbage collected
soutaro Apr 2, 2026
f5e6d8e
Drop `…` comment
soutaro Apr 2, 2026
cc54708
Add `use`
soutaro Apr 2, 2026
9fb0de4
Get owner_id through declaration
soutaro Apr 2, 2026
fe2e52d
Fixup tests
soutaro Apr 2, 2026
9a0fbad
dedup deep_dealias_method result
soutaro Apr 2, 2026
7f41cb3
cargo fmt
soutaro Apr 2, 2026
e9942f7
Add GraphTest.source_at_offset
soutaro Apr 2, 2026
c5c9d07
Add panics section
soutaro Apr 2, 2026
4d495ef
Move `source_at` to `Offset`
soutaro Apr 8, 2026
fce9732
Add test for singleton classes
soutaro Apr 13, 2026
536102e
It panics instead of returning NULL
soutaro Apr 13, 2026
4494f5c
delete skills.json
soutaro Apr 13, 2026
aeeebb3
Simpler deep_dealias_method return value
soutaro Apr 13, 2026
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
27 changes: 27 additions & 0 deletions ext/rubydex/definition.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "reference.h"
#include "ruby/internal/scan_args.h"
#include "rustbindings.h"
#include "signature.h"

static VALUE mRubydex;
static VALUE cInclude;
Expand Down Expand Up @@ -239,6 +240,30 @@ static VALUE rdxr_definition_mixins(VALUE self) {
return ary;
}

// 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);
}

// MethodAliasDefinition#signatures -> [Rubydex::Signature]
static VALUE rdxr_method_alias_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_method_alias_definition_signatures(graph, data->id);
return rdxi_signatures_to_ruby(arr);
}

void rdxi_initialize_definition(VALUE mod) {
mRubydex = mod;

Expand Down Expand Up @@ -273,12 +298,14 @@ void rdxi_initialize_definition(VALUE mod) {
cConstantVisibilityDefinition = rb_define_class_under(mRubydex, "ConstantVisibilityDefinition", cDefinition);
cMethodVisibilityDefinition = rb_define_class_under(mRubydex, "MethodVisibilityDefinition", 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);
cGlobalVariableDefinition = rb_define_class_under(mRubydex, "GlobalVariableDefinition", cDefinition);
cInstanceVariableDefinition = rb_define_class_under(mRubydex, "InstanceVariableDefinition", cDefinition);
cClassVariableDefinition = rb_define_class_under(mRubydex, "ClassVariableDefinition", cDefinition);
cMethodAliasDefinition = rb_define_class_under(mRubydex, "MethodAliasDefinition", cDefinition);
rb_define_method(cMethodAliasDefinition, "signatures", rdxr_method_alias_definition_signatures, 0);
cGlobalVariableAliasDefinition = rb_define_class_under(mRubydex, "GlobalVariableAliasDefinition", cDefinition);
}
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);
}
72 changes: 72 additions & 0 deletions ext/rubydex/signature.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "signature.h"
#include "location.h"

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

static VALUE parameter_class_for_kind(ParameterKind kind) {
switch (kind) {
case ParameterKind_RequiredPositional: return cPositionalParameter;
case ParameterKind_OptionalPositional: return cOptionalPositionalParameter;
case ParameterKind_RestPositional: return cRestPositionalParameter;
case ParameterKind_Post: return cPostParameter;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
case ParameterKind_Post: return cPostParameter;
case ParameterKind_Post: return cPostParameter;

case ParameterKind_RequiredKeyword: return cKeywordParameter;
case ParameterKind_OptionalKeyword: return cOptionalKeywordParameter;
case ParameterKind_RestKeyword: return cRestKeywordParameter;
case ParameterKind_Forward: return cForwardParameter;
case ParameterKind_Block: return cBlockParameter;
default: rb_raise(rb_eRuntimeError, "Unknown ParameterKind: %d", kind);
}
}

VALUE rdxi_signatures_to_ruby(SignatureArray *arr) {
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 parameters = rb_ary_new_capa((long)sig_entry.parameters_len);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For a follow-up PR, potential performance optimization:

Parameter-less functions are pretty common. We might see some nice memory gains if we reuse the same empty array between them all.

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 = rb_str_intern(rb_utf8_str_new_cstr(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 signature = rb_class_new_instance(1, &parameters, 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);
cPostParameter = rb_define_class_under(cSignature, "PostParameter", 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);
cForwardParameter = rb_define_class_under(cSignature, "ForwardParameter", cParameter);
Comment thread
soutaro marked this conversation as resolved.
cBlockParameter = rb_define_class_under(cSignature, "BlockParameter", cParameter);
}
25 changes: 25 additions & 0 deletions ext/rubydex/signature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#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 cPostParameter;
extern VALUE cKeywordParameter;
extern VALUE cOptionalKeywordParameter;
extern VALUE cRestKeywordParameter;
extern VALUE cForwardParameter;
extern VALUE cBlockParameter;

// Convert a SignatureArray into a Ruby array of Rubydex::Signature objects.
// The SignatureArray is freed after conversion.
VALUE rdxi_signatures_to_ruby(SignatureArray *arr);

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 @@ -17,6 +17,7 @@
require "rubydex/failures"
require "rubydex/location"
require "rubydex/comment"
require "rubydex/signature"
require "rubydex/diagnostic"
require "rubydex/keyword"
require "rubydex/keyword_parameter"
Expand Down
27 changes: 27 additions & 0 deletions lib/rubydex/signature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Rubydex
class Signature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(Not for this PR, but in a follow-up)

In my experience, having all the parameters in a single array ends up making callers need to do lots of filtering (e.g. to find just positional args, just keyword args, pull out the block param, etc.).

As the need arises, I would encourage adding convenience methods like positional_parameters, keyword_parameters, block, etc.

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

#: (Array[Parameter]) -> void
def initialize(parameters)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No return value?

@parameters = parameters
end
end
end
37 changes: 5 additions & 32 deletions rust/rubydex-sys/src/declaration_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,42 +178,15 @@ pub unsafe extern "C" fn rdx_declaration_find_member(

with_graph(pointer, |graph| {
let id = DeclarationId::new(declaration_id);
let member_id = StringId::from(member_str.as_str());

let Some(Declaration::Namespace(decl)) = graph.declarations().get(&id) else {
let Some(member_decl_id) = rubydex::query::find_member_in_ancestors(graph, id, member_id, only_inherited)
else {
return ptr::null();
};

let member_id = StringId::from(member_str.as_str());
let mut found_main_namespace = false;

decl.ancestors()
.iter()
.find_map(|ancestor| match ancestor {
Ancestor::Complete(ancestor_id) => {
// When only_inherited, skip self and prepended modules
if only_inherited {
let is_self = *ancestor_id == id;
if is_self {
found_main_namespace = true;
}
if is_self || !found_main_namespace {
return None;
}
}

let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();

if let Some(member_decl_id) = ancestor_decl.member(&member_id) {
return Some((member_decl_id, graph.declarations().get(member_decl_id).unwrap()));
}

None
}
Ancestor::Partial(_) => None,
})
.map_or(ptr::null(), |(member_decl_id, member_decl)| {
Box::into_raw(Box::new(CDeclaration::from_declaration(*member_decl_id, member_decl))).cast_const()
})
let member_decl = graph.declarations().get(&member_decl_id).unwrap();
Box::into_raw(Box::new(CDeclaration::from_declaration(member_decl_id, member_decl))).cast_const()
})
}

Expand Down
1 change: 1 addition & 0 deletions rust/rubydex-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@ pub mod graph_api;
pub mod location_api;
pub mod name_api;
pub mod reference_api;
pub mod signature_api;
pub mod utils;
Loading
Loading