Skip to content

Commit b1249fd

Browse files
committed
Add C API function rb_funcall_with_block_kw
1 parent 67ac948 commit b1249fd

File tree

6 files changed

+132
-10
lines changed

6 files changed

+132
-10
lines changed

lib/truffle/truffle/cext.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,10 @@ def rb_funcall_with_block(recv, meth, argv, block)
903903
Primitive.public_send_argv_without_cext_lock(recv, meth, argv, block)
904904
end
905905

906+
def rb_funcall_with_block_keywords(recv, meth, argv, block)
907+
Primitive.public_send_argv_keywords_without_cext_lock(recv, meth, argv, block)
908+
end
909+
906910
def rb_funcallv_public(recv, meth, argv)
907911
Primitive.public_send_argv_without_cext_lock(recv, meth, argv, nil)
908912
end

spec/ruby/optional/capi/ext/kernel_spec.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,12 @@ static VALUE kernel_spec_rb_funcallv_public(VALUE self, VALUE obj, VALUE method)
340340
return rb_funcallv_public(obj, SYM2ID(method), 0, NULL);
341341
}
342342

343-
static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE block) {
344-
return rb_funcall_with_block(obj, SYM2ID(method), 0, NULL, block);
343+
static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE args, VALUE block) {
344+
return rb_funcall_with_block(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), block);
345+
}
346+
347+
static VALUE kernel_spec_rb_funcall_with_block_kw(VALUE self, VALUE obj, VALUE method, VALUE args, VALUE block) {
348+
return rb_funcall_with_block_kw(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), block, RB_PASS_KEYWORDS);
345349
}
346350

347351
static VALUE kernel_spec_rb_funcall_many_args(VALUE self, VALUE obj, VALUE method) {
@@ -397,7 +401,8 @@ void Init_kernel_spec(void) {
397401
#endif
398402
rb_define_method(cls, "rb_funcallv_public", kernel_spec_rb_funcallv_public, 2);
399403
rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2);
400-
rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3);
404+
rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 4);
405+
rb_define_method(cls, "rb_funcall_with_block_kw", kernel_spec_rb_funcall_with_block_kw, 4);
401406
}
402407

403408
#ifdef __cplusplus
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class CApiKernelSpecs
2+
class ClassWithPublicMethod
3+
def public_method(*, **)
4+
0
5+
end
6+
end
7+
8+
class ClassWithPrivateMethod
9+
private def private_method(*, **)
10+
0
11+
end
12+
end
13+
14+
class ClassWithProtectedMethod
15+
protected def protected_method(*, **)
16+
0
17+
end
18+
end
19+
end

spec/ruby/optional/capi/kernel_spec.rb

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require_relative 'spec_helper'
2+
require_relative 'fixtures/kernel'
23

34
kernel_path = load_extension("kernel")
45

@@ -597,6 +598,16 @@ def sum(a, b)
597598
@s.rb_funcallv(self, :empty, []).should == 42
598599
@s.rb_funcallv(self, :sum, [1, 2]).should == 3
599600
end
601+
602+
it "calls a private method" do
603+
object = CApiKernelSpecs::ClassWithPrivateMethod.new
604+
@s.rb_funcallv(object, :private_method, []).should == 0
605+
end
606+
607+
it "calls a protected method" do
608+
object = CApiKernelSpecs::ClassWithProtectedMethod.new
609+
@s.rb_funcallv(object, :protected_method, []).should == 0
610+
end
600611
end
601612

602613
ruby_version_is "3.0" do
@@ -621,6 +632,16 @@ def m(*args, **kwargs)
621632
@s.rb_funcallv_kw(self, :m, [42])
622633
}.should raise_error(TypeError, 'no implicit conversion of Integer into Hash')
623634
end
635+
636+
it "calls a private method" do
637+
object = CApiKernelSpecs::ClassWithPrivateMethod.new
638+
@s.rb_funcallv_kw(object, :private_method, [{}]).should == 0
639+
end
640+
641+
it "calls a protected method" do
642+
object = CApiKernelSpecs::ClassWithProtectedMethod.new
643+
@s.rb_funcallv_kw(object, :protected_method, [{}]).should == 0
644+
end
624645
end
625646

626647
describe "rb_keyword_given_p" do
@@ -676,21 +697,56 @@ def many_args(*args)
676697
end
677698

678699
describe 'rb_funcall_with_block' do
679-
before :each do
700+
it "calls a method with block" do
680701
@obj = Object.new
681702
class << @obj
682-
def method_public; yield end
683-
def method_private; yield end
684-
private :method_private
703+
def method_public(*args); [args, yield] end
685704
end
705+
706+
@s.rb_funcall_with_block(@obj, :method_public, [1, 2], proc { :result }).should == [[1, 2], :result]
686707
end
687708

688-
it "calls a method with block" do
689-
@s.rb_funcall_with_block(@obj, :method_public, proc { :result }).should == :result
709+
it "does not call a private method" do
710+
object = CApiKernelSpecs::ClassWithPrivateMethod.new
711+
712+
-> {
713+
@s.rb_funcall_with_block(object, :private_method, [], proc { })
714+
}.should raise_error(NoMethodError, /private/)
715+
end
716+
717+
it "does not call a protected method" do
718+
object = CApiKernelSpecs::ClassWithProtectedMethod.new
719+
720+
-> {
721+
@s.rb_funcall_with_block(object, :protected_method, [], proc { })
722+
}.should raise_error(NoMethodError, /protected/)
723+
end
724+
end
725+
726+
describe 'rb_funcall_with_block_kw' do
727+
it "calls a method with keyword arguments and a block" do
728+
@obj = Object.new
729+
class << @obj
730+
def method_public(*args, **kw, &block); [args, kw, block.call] end
731+
end
732+
733+
@s.rb_funcall_with_block_kw(@obj, :method_public, [1, 2, {a: 2}], proc { :result }).should == [[1, 2], {a: 2}, :result]
690734
end
691735

692736
it "does not call a private method" do
693-
-> { @s.rb_funcall_with_block(@obj, :method_private, proc { :result }) }.should raise_error(NoMethodError, /private/)
737+
object = CApiKernelSpecs::ClassWithPrivateMethod.new
738+
739+
-> {
740+
@s.rb_funcall_with_block_kw(object, :private_method, [{}], proc { })
741+
}.should raise_error(NoMethodError, /private/)
742+
end
743+
744+
it "does not call a protected method" do
745+
object = CApiKernelSpecs::ClassWithProtectedMethod.new
746+
747+
-> {
748+
@s.rb_funcall_with_block_kw(object, :protected_method, [{}], proc { })
749+
}.should raise_error(NoMethodError, /protected/)
694750
end
695751
end
696752
end

src/main/c/cext/call.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VAL
106106
rb_tr_unwrap(pass_procval)));
107107
}
108108

109+
VALUE rb_funcall_with_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE procval, int kw_splat) {
110+
if (kw_splat && argc > 0) {
111+
return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_funcall_with_block_keywords",
112+
rb_tr_unwrap(recv),
113+
rb_tr_unwrap(ID2SYM(mid)),
114+
polyglot_from_VALUE_array(argv, argc),
115+
rb_tr_unwrap(procval)));
116+
} else {
117+
return rb_funcall_with_block(recv, mid, argc, argv, procval);
118+
}
119+
}
120+
109121
VALUE rb_yield_splat(VALUE values) {
110122
if (rb_block_given_p()) {
111123
return RUBY_CEXT_INVOKE("rb_yield_splat", values);

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,32 @@ protected Object publicSendWithoutLock(
411411
}
412412
}
413413

414+
@Primitive(name = "public_send_argv_keywords_without_cext_lock")
415+
public abstract static class PublicSendARGVKeywordsWithoutCExtLockNode extends SendWithoutCExtLockBaseNode {
416+
@Specialization
417+
protected Object sendWithoutCExtLock(
418+
VirtualFrame frame, Object receiver, RubySymbol method, Object argv, Object block,
419+
@Cached UnwrapCArrayNode unwrapCArrayNode,
420+
@Cached HashCastNode hashCastNode,
421+
@Cached InlinedConditionProfile emptyProfile,
422+
@Cached(parameters = "PUBLIC") DispatchNode dispatchNode,
423+
@Cached InlinedConditionProfile ownedProfile) {
424+
Object[] args = unwrapCArrayNode.execute(argv);
425+
426+
// Remove empty kwargs in the caller, so the callee does not need to care about this special case
427+
final RubyHash keywords = hashCastNode.execute(ArrayUtils.getLast(args));
428+
if (emptyProfile.profile(this, keywords.empty())) {
429+
args = LiteralCallNode.removeEmptyKeywordArguments(args);
430+
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
431+
dispatchNode, ownedProfile);
432+
} else {
433+
return sendWithoutCExtLock(frame, receiver, method, block,
434+
KeywordArgumentsDescriptorManager.EMPTY, args,
435+
dispatchNode, ownedProfile);
436+
}
437+
}
438+
}
439+
414440
@Primitive(name = "cext_mark_object_on_call_exit")
415441
public abstract static class MarkObjectOnCallExit extends PrimitiveArrayArgumentsNode {
416442

0 commit comments

Comments
 (0)