Skip to content

Commit

Permalink
[GR-59866] Add rb_data_define() C function
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/4434
  • Loading branch information
andrykonchin committed Jan 14, 2025
2 parents 97ebf9b + 56082b7 commit 7e414ad
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Compatibility:
* Remove deprecated `Encoding#replicate` method (#3681, @rwstauner).
* Add `ObjectSpace::WeakMap#delete` (#3681, @andrykonchin).
* `Kernel#lambda` with now raises `ArgumentError` when given a non-lambda, non-literal block (#3681, @Th3-M4jor).
* Add `rb_data_define()` to define Data (#3681, @andrykonchin).

Performance:

Expand Down
14 changes: 14 additions & 0 deletions lib/cext/include/ruby/internal/intern/struct.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ RBIMPL_ATTR_NONNULL((2))
*/
VALUE rb_struct_define_without_accessor_under(VALUE outer, const char *class_name, VALUE super, rb_alloc_func_t alloc, ...);

#ifdef TRUFFLERUBY
VALUE rb_tr_data_define_va_list(VALUE super, va_list args);
#endif

/**
* Defines an anonymous data class.
*
Expand All @@ -252,7 +256,17 @@ VALUE rb_struct_define_without_accessor_under(VALUE outer, const char *class_nam
* @exception rb_eArgError Duplicated field name.
* @return The defined class.
*/
#ifdef TRUFFLERUBY
static inline VALUE rb_data_define(VALUE super, ...) {
va_list args;
va_start(args, super);
VALUE result = rb_tr_data_define_va_list(super == 0 ? Qnil : super, args);
va_end(args);
return result;
}
#else
VALUE rb_data_define(VALUE super, ...);
#endif

RBIMPL_SYMBOL_EXPORT_END()

Expand Down
2 changes: 1 addition & 1 deletion lib/cext/include/truffleruby/truffleruby-abi-version.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
// $RUBY_VERSION must be the same as TruffleRuby.LANGUAGE_VERSION.
// $ABI_NUMBER starts at 1 and is incremented for every ABI-incompatible change.

#define TRUFFLERUBY_ABI_VERSION "3.3.5.8"
#define TRUFFLERUBY_ABI_VERSION "3.3.5.9"

#endif
11 changes: 11 additions & 0 deletions lib/truffle/truffle/cext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,17 @@ def rb_struct_new_no_splat(klass, args)
klass.new(*args)
end

def rb_data_define_no_splat(klass, attrs)
klass ||= Data
Truffle::Type.rb_check_type(klass, Class)

unless klass <= Data
raise TypeError, 'invalid Class for rb_data_define(), expected Data or a subclass of Data'
end

klass.define(*attrs)
end

def yield_no_block
raise LocalJumpError
end
Expand Down
24 changes: 20 additions & 4 deletions spec/ruby/optional/capi/ext/struct_spec.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static VALUE struct_spec_rb_struct_aset(VALUE self, VALUE st, VALUE key, VALUE v
}

/* Only allow setting three attributes, should be sufficient for testing. */
static VALUE struct_spec_struct_define(VALUE self, VALUE name,
static VALUE struct_spec_rb_struct_define(VALUE self, VALUE name,
VALUE attr1, VALUE attr2, VALUE attr3) {

const char *a1 = StringValuePtr(attr1);
Expand All @@ -42,7 +42,7 @@ static VALUE struct_spec_struct_define(VALUE self, VALUE name,
}

/* Only allow setting three attributes, should be sufficient for testing. */
static VALUE struct_spec_struct_define_under(VALUE self, VALUE outer,
static VALUE struct_spec_rb_struct_define_under(VALUE self, VALUE outer,
VALUE name, VALUE attr1, VALUE attr2, VALUE attr3) {

const char *nm = StringValuePtr(name);
Expand All @@ -62,17 +62,33 @@ static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st) {
return rb_struct_size(st);
}

/* Only allow setting three attributes, should be sufficient for testing. */
static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass,
VALUE attr1, VALUE attr2, VALUE attr3) {

const char *a1 = StringValuePtr(attr1);
const char *a2 = StringValuePtr(attr2);
const char *a3 = StringValuePtr(attr3);

if (superclass == Qnil) {
superclass = 0;
}

return rb_data_define(superclass, a1, a2, a3, NULL);
}

void Init_struct_spec(void) {
VALUE cls = rb_define_class("CApiStructSpecs", rb_cObject);
rb_define_method(cls, "rb_struct_aref", struct_spec_rb_struct_aref, 2);
rb_define_method(cls, "rb_struct_getmember", struct_spec_rb_struct_getmember, 2);
rb_define_method(cls, "rb_struct_s_members", struct_spec_rb_struct_s_members, 1);
rb_define_method(cls, "rb_struct_members", struct_spec_rb_struct_members, 1);
rb_define_method(cls, "rb_struct_aset", struct_spec_rb_struct_aset, 3);
rb_define_method(cls, "rb_struct_define", struct_spec_struct_define, 4);
rb_define_method(cls, "rb_struct_define_under", struct_spec_struct_define_under, 5);
rb_define_method(cls, "rb_struct_define", struct_spec_rb_struct_define, 4);
rb_define_method(cls, "rb_struct_define_under", struct_spec_rb_struct_define_under, 5);
rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4);
rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1);
rb_define_method(cls, "rb_data_define", struct_spec_rb_data_define, 4);
}

#ifdef __cplusplus
Expand Down
49 changes: 49 additions & 0 deletions spec/ruby/optional/capi/struct_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,52 @@
end
end
end

ruby_version_is "3.3" do
describe "C-API Data function" do
before :each do
@s = CApiStructSpecs.new
end

describe "rb_data_define" do
it "returns a subclass of Data class when passed nil as the first argument" do
klass = @s.rb_data_define(nil, "a", "b", "c")

klass.should.is_a? Class
klass.superclass.should == Data
end

it "returns a subclass of a class when passed as the first argument" do
superclass = Class.new(Data)
klass = @s.rb_data_define(superclass, "a", "b", "c")

klass.should.is_a? Class
klass.superclass.should == superclass
end

it "creates readers for the members" do
klass = @s.rb_data_define(nil, "a", "b", "c")
obj = klass.new(1, 2, 3)

obj.a.should == 1
obj.b.should == 2
obj.c.should == 3
end

it "returns the member names as Symbols" do
klass = @s.rb_data_define(nil, "a", "b", "c")
obj = klass.new(0, 0, 0)

obj.members.should == [:a, :b, :c]
end

it "raises an ArgumentError if arguments contain duplicate member name" do
-> { @s.rb_data_define(nil, "a", "b", "a") }.should raise_error(ArgumentError)
end

it "raises when first argument is not a class" do
-> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)")
end
end
end
end
11 changes: 11 additions & 0 deletions src/main/c/cext/struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@ VALUE rb_struct_members(VALUE s) {
VALUE rb_struct_size(VALUE s) {
return RUBY_INVOKE(s, "size");
}

VALUE rb_tr_data_define_va_list(VALUE super, va_list args) {
VALUE ary = rb_ary_new();
int i = 0;
char *arg;
while ((arg = va_arg(args, char*)) != NULL) {
rb_ary_push(ary, rb_str_new_cstr(arg));
i++;
}
return RUBY_CEXT_INVOKE("rb_data_define_no_splat", super, ary);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* Also useful to split Ruby methods which do something like `if obj.is_a?(Foo) then foo else bar end`
* such as NoBorderImagePadded#index used by NoBorderImage#[] in the image-demo benchmarks. */
@GenerateUncached
@ReportPolymorphism // see commment above
@ReportPolymorphism // see comment above
public abstract class IsANode extends RubyBaseNode {

@NeverDefault
Expand Down

0 comments on commit 7e414ad

Please sign in to comment.