Skip to content

Commit 88a0660

Browse files
committed
Implement and spec rb_funcallv_kw()
1 parent 8b32c02 commit 88a0660

File tree

10 files changed

+121
-13
lines changed

10 files changed

+121
-13
lines changed

lib/cext/ABI_check.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
4

lib/truffle/truffle/cext.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,10 @@ def rb_funcallv(recv, meth, argv)
875875
Primitive.send_argv_without_cext_lock(recv, meth, argv, nil)
876876
end
877877

878+
def rb_funcallv_keywords(recv, meth, argv)
879+
Primitive.send_argv_keywords_without_cext_lock(recv, meth, argv, nil)
880+
end
881+
878882
def rb_funcall(recv, meth, n, *args)
879883
Primitive.send_without_cext_lock(recv, meth, args, nil)
880884
end

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ static VALUE kernel_spec_rb_make_backtrace(VALUE self) {
318318
return rb_make_backtrace();
319319
}
320320

321+
static VALUE kernel_spec_rb_funcallv(VALUE self, VALUE obj, VALUE method, VALUE args) {
322+
return rb_funcallv(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args));
323+
}
324+
325+
#ifdef RUBY_VERSION_IS_3_0
326+
static VALUE kernel_spec_rb_funcallv_kw(VALUE self, VALUE obj, VALUE method, VALUE args) {
327+
return rb_funcallv_kw(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), RB_PASS_KEYWORDS);
328+
}
329+
#endif
330+
321331
static VALUE kernel_spec_rb_funcallv_public(VALUE self, VALUE obj, VALUE method) {
322332
return rb_funcallv_public(obj, SYM2ID(method), 0, NULL);
323333
}
@@ -371,6 +381,10 @@ void Init_kernel_spec(void) {
371381
rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1);
372382
rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1);
373383
rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0);
384+
rb_define_method(cls, "rb_funcallv", kernel_spec_rb_funcallv, 3);
385+
#ifdef RUBY_VERSION_IS_3_0
386+
rb_define_method(cls, "rb_funcallv_kw", kernel_spec_rb_funcallv_kw, 3);
387+
#endif
374388
rb_define_method(cls, "rb_funcallv_public", kernel_spec_rb_funcallv_public, 2);
375389
rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2);
376390
rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3);

spec/ruby/optional/capi/kernel_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,46 @@ def proc_caller
567567
end
568568
end
569569

570+
describe "rb_funcallv" do
571+
def empty
572+
42
573+
end
574+
575+
def sum(a, b)
576+
a + b
577+
end
578+
579+
it "calls a method" do
580+
@s.rb_funcallv(self, :empty, []).should == 42
581+
@s.rb_funcallv(self, :sum, [1, 2]).should == 3
582+
end
583+
end
584+
585+
ruby_version_is "3.0" do
586+
describe "rb_funcallv_kw" do
587+
it "passes keyword arguments to the callee" do
588+
def m(*args, **kwargs)
589+
[args, kwargs]
590+
end
591+
592+
@s.rb_funcallv_kw(self, :m, [{}]).should == [[], {}]
593+
@s.rb_funcallv_kw(self, :m, [{a: 1}]).should == [[], {a: 1}]
594+
@s.rb_funcallv_kw(self, :m, [{b: 2}, {a: 1}]).should == [[{b: 2}], {a: 1}]
595+
@s.rb_funcallv_kw(self, :m, [{b: 2}, {}]).should == [[{b: 2}], {}]
596+
end
597+
598+
it "raises TypeError if the last argument is not a Hash" do
599+
def m(*args, **kwargs)
600+
[args, kwargs]
601+
end
602+
603+
-> {
604+
@s.rb_funcallv_kw(self, :m, [42])
605+
}.should raise_error(TypeError, 'no implicit conversion of Integer into Hash')
606+
end
607+
end
608+
end
609+
570610
describe "rb_funcallv_public" do
571611
before :each do
572612
@obj = Object.new
@@ -580,6 +620,7 @@ def method_private; :method_private end
580620
it "calls a public method" do
581621
@s.rb_funcallv_public(@obj, :method_public).should == :method_public
582622
end
623+
583624
it "does not call a private method" do
584625
-> { @s.rb_funcallv_public(@obj, :method_private) }.should raise_error(NoMethodError, /private/)
585626
end
@@ -599,6 +640,7 @@ def many_args(*args)
599640
@s.rb_funcall_many_args(@obj, :many_args).should == 15.downto(1).to_a
600641
end
601642
end
643+
602644
describe 'rb_funcall_with_block' do
603645
before :each do
604646
@obj = Object.new

src/main/c/cext/call.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ VALUE rb_funcallv(VALUE object, ID name, int args_count, const VALUE *args) {
3131
}
3232

3333
VALUE rb_funcallv_kw(VALUE object, ID name, int args_count, const VALUE *args, int kw_splat) {
34-
// Ignoring kw_splat for now
35-
return rb_funcallv(object, name, args_count, args);
34+
if (kw_splat && args_count > 0) {
35+
return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_funcallv_keywords",
36+
rb_tr_unwrap(object),
37+
rb_tr_unwrap(ID2SYM(name)),
38+
polyglot_from_VALUE_array(args, args_count)));
39+
} else {
40+
return rb_funcallv(object, name, args_count, args);
41+
}
3642
}
3743

3844
VALUE rb_funcallv_public(VALUE object, ID name, int args_count, const VALUE *args) {

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

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import org.truffleruby.core.MarkingService.ExtensionCallStack;
3535
import org.truffleruby.core.MarkingServiceNodes;
3636
import org.truffleruby.core.array.ArrayToObjectArrayNode;
37+
import org.truffleruby.core.array.ArrayUtils;
3738
import org.truffleruby.core.array.RubyArray;
39+
import org.truffleruby.core.cast.HashCastNode;
3840
import org.truffleruby.core.encoding.Encodings;
3941
import org.truffleruby.core.encoding.RubyEncoding;
4042
import org.truffleruby.core.exception.ErrnoErrorNode;
@@ -45,6 +47,7 @@
4547
import org.truffleruby.core.format.exceptions.InvalidFormatException;
4648
import org.truffleruby.core.format.rbsprintf.RBSprintfCompiler;
4749
import org.truffleruby.core.hash.HashingNodes;
50+
import org.truffleruby.core.hash.RubyHash;
4851
import org.truffleruby.core.klass.RubyClass;
4952
import org.truffleruby.core.module.MethodLookupResult;
5053
import org.truffleruby.core.module.ModuleNodes.ConstSetUncheckedNode;
@@ -84,7 +87,9 @@
8487
import org.truffleruby.language.RubyNode;
8588
import org.truffleruby.language.RubyRootNode;
8689
import org.truffleruby.language.Visibility;
90+
import org.truffleruby.language.arguments.ArgumentsDescriptor;
8791
import org.truffleruby.language.arguments.EmptyArgumentsDescriptor;
92+
import org.truffleruby.language.arguments.KeywordArgumentsDescriptor;
8893
import org.truffleruby.language.arguments.RubyArguments;
8994
import org.truffleruby.language.backtrace.Backtrace;
9095
import org.truffleruby.language.constants.GetConstantNode;
@@ -93,6 +98,7 @@
9398
import org.truffleruby.language.control.BreakID;
9499
import org.truffleruby.language.control.RaiseException;
95100
import org.truffleruby.language.dispatch.DispatchNode;
101+
import org.truffleruby.language.dispatch.LiteralCallNode;
96102
import org.truffleruby.language.library.RubyStringLibrary;
97103
import org.truffleruby.language.methods.DeclarationContext;
98104
import org.truffleruby.language.methods.InternalMethod;
@@ -192,7 +198,8 @@ protected int getCacheLimit() {
192198

193199
public abstract static class SendWithoutCExtLockBaseNode extends PrimitiveArrayArgumentsNode {
194200
public Object sendWithoutCExtLock(VirtualFrame frame, Object receiver, RubySymbol method, Object block,
195-
DispatchNode dispatchNode, ConditionProfile ownedProfile, Object[] args) {
201+
ArgumentsDescriptor descriptor, Object[] args,
202+
DispatchNode dispatchNode, ConditionProfile ownedProfile) {
196203
if (getContext().getOptions().CEXT_LOCK) {
197204
final ReentrantLock lock = getContext().getCExtensionsLock();
198205
boolean owned = ownedProfile.profile(lock.isHeldByCurrentThread());
@@ -201,14 +208,15 @@ public Object sendWithoutCExtLock(VirtualFrame frame, Object receiver, RubySymbo
201208
MutexOperations.unlockInternal(lock);
202209
}
203210
try {
204-
return dispatchNode.callWithFrameAndBlock(frame, receiver, method.getString(), block, args);
211+
return dispatchNode.callWithFrameAndBlock(frame, receiver, method.getString(), block,
212+
descriptor, args);
205213
} finally {
206214
if (owned) {
207215
MutexOperations.internalLockEvenWithException(getContext(), lock, this);
208216
}
209217
}
210218
} else {
211-
return dispatchNode.callWithFrameAndBlock(frame, receiver, method.getString(), block, args);
219+
return dispatchNode.callWithFrameAndBlock(frame, receiver, method.getString(), block, descriptor, args);
212220
}
213221
}
214222
}
@@ -222,7 +230,8 @@ protected Object sendWithoutCExtLock(
222230
@Cached DispatchNode dispatchNode,
223231
@Cached ConditionProfile ownedProfile) {
224232
final Object[] args = arrayToObjectArrayNode.executeToObjectArray(argsArray);
225-
return sendWithoutCExtLock(frame, receiver, method, block, dispatchNode, ownedProfile, args);
233+
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
234+
dispatchNode, ownedProfile);
226235
}
227236

228237
}
@@ -236,7 +245,33 @@ protected Object sendWithoutCExtLock(
236245
@Cached DispatchNode dispatchNode,
237246
@Cached ConditionProfile ownedProfile) {
238247
final Object[] args = unwrapCArrayNode.execute(argv);
239-
return sendWithoutCExtLock(frame, receiver, method, block, dispatchNode, ownedProfile, args);
248+
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
249+
dispatchNode, ownedProfile);
250+
}
251+
}
252+
253+
@Primitive(name = "send_argv_keywords_without_cext_lock")
254+
public abstract static class SendARGVKeywordsWithoutCExtLockNode extends SendWithoutCExtLockBaseNode {
255+
@Specialization
256+
protected Object sendWithoutCExtLock(
257+
VirtualFrame frame, Object receiver, RubySymbol method, Object argv, Object block,
258+
@Cached UnwrapCArrayNode unwrapCArrayNode,
259+
@Cached HashCastNode hashCastNode,
260+
@Cached ConditionProfile emptyProfile,
261+
@Cached DispatchNode dispatchNode,
262+
@Cached ConditionProfile ownedProfile) {
263+
Object[] args = unwrapCArrayNode.execute(argv);
264+
265+
// Remove empty kwargs in the caller, so the callee does not need to care about this special case
266+
final RubyHash keywords = hashCastNode.execute(ArrayUtils.getLast(args));
267+
if (emptyProfile.profile(keywords.empty())) {
268+
args = LiteralCallNode.removeEmptyKeywordArguments(args);
269+
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
270+
dispatchNode, ownedProfile);
271+
} else {
272+
return sendWithoutCExtLock(frame, receiver, method, block, KeywordArgumentsDescriptor.INSTANCE, args,
273+
dispatchNode, ownedProfile);
274+
}
240275
}
241276
}
242277

@@ -249,7 +284,8 @@ protected Object publicSendWithoutLock(
249284
@Cached(parameters = "PUBLIC") DispatchNode dispatchNode,
250285
@Cached ConditionProfile ownedProfile) {
251286
final Object[] args = unwrapCArrayNode.execute(argv);
252-
return sendWithoutCExtLock(frame, receiver, method, block, dispatchNode, ownedProfile, args);
287+
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
288+
dispatchNode, ownedProfile);
253289
}
254290
}
255291

src/main/java/org/truffleruby/core/cast/HashCastNode.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@
2525
@NodeChild(value = "child", type = RubyNode.class)
2626
public abstract class HashCastNode extends RubyContextSourceNode {
2727

28+
public static HashCastNode create() {
29+
return HashCastNodeGen.create(null);
30+
}
31+
2832
protected abstract RubyNode getChild();
2933

34+
public abstract RubyHash execute(Object value);
35+
3036
@Specialization
3137
protected RubyHash castHash(RubyHash hash) {
3238
return hash;

src/main/java/org/truffleruby/language/arguments/RubyArguments.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public static boolean assertFrameArguments(Object[] rubyArgs) {
7777

7878
if (descriptor instanceof KeywordArgumentsDescriptor) {
7979
final Object lastArgument = getLastArgument(rubyArgs);
80-
assert lastArgument instanceof RubyHash;
80+
assert lastArgument instanceof RubyHash : "the last frame argument must be a Hash with a kwargs descriptor";
8181
assert !((RubyHash) lastArgument).empty() : "empty kwargs should not have been passed";
8282
}
8383

src/main/java/org/truffleruby/language/dispatch/DispatchNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,11 @@ public Object callWithFrame(Frame frame, Object receiver, String method, Object[
261261
}
262262

263263
public final Object callWithFrameAndBlock(Frame frame, Object receiver, String methodName, Object block,
264-
Object[] arguments) {
264+
ArgumentsDescriptor descriptor, Object[] arguments) {
265265
final Object[] rubyArgs = RubyArguments.allocate(arguments.length);
266266
RubyArguments.setSelf(rubyArgs, receiver);
267267
RubyArguments.setBlock(rubyArgs, block);
268-
RubyArguments.setDescriptor(rubyArgs, EmptyArgumentsDescriptor.INSTANCE);
268+
RubyArguments.setDescriptor(rubyArgs, descriptor);
269269
RubyArguments.setArguments(rubyArgs, arguments);
270270
return dispatch(frame, receiver, methodName, rubyArgs);
271271
}

src/main/java/org/truffleruby/language/dispatch/LiteralCallNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected boolean emptyKeywordArguments(Object[] args) {
8585
}
8686

8787
// NOTE: args is either frame args or user args
88-
protected Object[] removeEmptyKeywordArguments(Object[] args) {
88+
public static Object[] removeEmptyKeywordArguments(Object[] args) {
8989
return ArrayUtils.extractRange(args, 0, args.length - 1);
9090
}
9191

0 commit comments

Comments
 (0)