Skip to content

Commit 536661d

Browse files
committed
[GR-37597] Copy the CRuby bug/behavior that the ruby2_keyword flag on a Hash is not reset for caller(*args) -> callee(*args) calls
PullRequest: truffleruby/3279
2 parents d2a9ae8 + 103d4da commit 536661d

File tree

19 files changed

+300
-113
lines changed

19 files changed

+300
-113
lines changed

spec/mspec/lib/mspec/expectations/expectations.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ def self.fail_predicate(receiver, predicate, args, block, result, expectation)
3232
result_to_s = MSpec.format(result)
3333
raise SpecExpectationNotMetError, "Expected #{receiver_to_s}#{predicate_to_s}#{args_to_s}\n#{expectation} but was #{result_to_s}"
3434
end
35+
36+
def self.fail_single_arg_predicate(receiver, predicate, arg, result, expectation)
37+
fail_predicate(receiver, predicate, [arg], nil, result, expectation)
38+
end
3539
end

spec/mspec/lib/mspec/matchers/base.rb

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,24 @@ def initialize(actual)
1616
end
1717

1818
def ==(expected)
19-
method_missing(:==, expected)
19+
result = @actual == expected
20+
unless result
21+
::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be truthy")
22+
end
2023
end
2124

2225
def !=(expected)
23-
method_missing(:!=, expected)
26+
result = @actual != expected
27+
unless result
28+
::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be truthy")
29+
end
2430
end
2531

2632
def equal?(expected)
27-
method_missing(:equal?, expected)
33+
result = @actual.equal?(expected)
34+
unless result
35+
::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be truthy")
36+
end
2837
end
2938

3039
def method_missing(name, *args, &block)
@@ -41,15 +50,24 @@ def initialize(actual)
4150
end
4251

4352
def ==(expected)
44-
method_missing(:==, expected)
53+
result = @actual == expected
54+
if result
55+
::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be falsy")
56+
end
4557
end
4658

4759
def !=(expected)
48-
method_missing(:!=, expected)
60+
result = @actual != expected
61+
if result
62+
::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be falsy")
63+
end
4964
end
5065

5166
def equal?(expected)
52-
method_missing(:equal?, expected)
67+
result = @actual.equal?(expected)
68+
if result
69+
::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be falsy")
70+
end
5371
end
5472

5573
def method_missing(name, *args, &block)

spec/ruby/core/module/ruby2_keywords_spec.rb

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33

44
ruby_version_is "2.7" do
55
describe "Module#ruby2_keywords" do
6-
it "marks the final hash argument as keyword hash" do
7-
obj = Object.new
8-
9-
obj.singleton_class.class_exec do
10-
def foo(*a) a.last end
11-
ruby2_keywords :foo
6+
class << self
7+
ruby2_keywords def mark(*args)
8+
args
129
end
10+
end
1311

14-
last = obj.foo(1, 2, a: "a")
12+
it "marks the final hash argument as keyword hash" do
13+
last = mark(1, 2, a: "a").last
1514
Hash.ruby2_keywords_hash?(last).should == true
1615
end
1716

@@ -21,74 +20,121 @@ def foo(*a) a.last end
2120
def regular(*args)
2221
args.last
2322
end
24-
25-
ruby2_keywords def foo(*args)
26-
args.last
27-
end
2823
end
2924

3025
h = {a: 1}
3126
ruby_version_is "3.0" do
3227
obj.regular(**h).should.equal?(h)
3328
end
3429

35-
last = obj.foo(**h)
30+
last = mark(**h).last
3631
Hash.ruby2_keywords_hash?(last).should == true
3732
Hash.ruby2_keywords_hash?(h).should == false
3833

39-
last2 = obj.foo(**last) # last is already marked
34+
last2 = mark(**last).last # last is already marked
4035
Hash.ruby2_keywords_hash?(last2).should == true
4136
Hash.ruby2_keywords_hash?(last).should == true
4237
last2.should_not.equal?(last)
4338
Hash.ruby2_keywords_hash?(h).should == false
4439
end
4540

46-
it "makes a copy and unmark at the call site when calling with marked *args" do
41+
it "makes a copy and unmark the Hash when calling a method taking (arg)" do
4742
obj = Object.new
4843
obj.singleton_class.class_exec do
49-
ruby2_keywords def foo(*args)
50-
args
51-
end
52-
5344
def single(arg)
5445
arg
5546
end
47+
end
5648

57-
def splat(*args)
58-
args.last
59-
end
49+
h = { a: 1 }
50+
args = mark(**h)
51+
marked = args.last
52+
Hash.ruby2_keywords_hash?(marked).should == true
53+
54+
after_usage = obj.single(*args)
55+
after_usage.should == h
56+
after_usage.should_not.equal?(h)
57+
after_usage.should_not.equal?(marked)
58+
Hash.ruby2_keywords_hash?(after_usage).should == false
59+
Hash.ruby2_keywords_hash?(marked).should == true
60+
end
6061

62+
it "makes a copy and unmark the Hash when calling a method taking (**kw)" do
63+
obj = Object.new
64+
obj.singleton_class.class_exec do
6165
def kwargs(**kw)
6266
kw
6367
end
6468
end
6569

6670
h = { a: 1 }
67-
args = obj.foo(**h)
71+
args = mark(**h)
6872
marked = args.last
6973
Hash.ruby2_keywords_hash?(marked).should == true
7074

71-
after_usage = obj.single(*args)
75+
after_usage = obj.kwargs(*args)
7276
after_usage.should == h
7377
after_usage.should_not.equal?(h)
7478
after_usage.should_not.equal?(marked)
7579
Hash.ruby2_keywords_hash?(after_usage).should == false
7680
Hash.ruby2_keywords_hash?(marked).should == true
81+
end
82+
83+
# https://bugs.ruby-lang.org/issues/18625
84+
it "does NOT copy the Hash when calling a method taking (*args)" do
85+
obj = Object.new
86+
obj.singleton_class.class_exec do
87+
def splat(*args)
88+
args.last
89+
end
90+
91+
def splat1(arg, *args)
92+
args.last
93+
end
94+
95+
def proc_call(*args)
96+
-> *args { args.last }.call(*args)
97+
end
98+
end
99+
100+
h = { a: 1 }
101+
args = mark(**h)
102+
marked = args.last
103+
Hash.ruby2_keywords_hash?(marked).should == true
77104

78105
after_usage = obj.splat(*args)
79106
after_usage.should == h
80107
after_usage.should_not.equal?(h)
81-
after_usage.should_not.equal?(marked)
82-
ruby_bug "#18625", ""..."3.3" do # might be fixed in 3.2
83-
Hash.ruby2_keywords_hash?(after_usage).should == false
84-
end
108+
after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
109+
Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
85110
Hash.ruby2_keywords_hash?(marked).should == true
86111

87-
after_usage = obj.kwargs(*args)
112+
args = mark(1, **h)
113+
marked = args.last
114+
after_usage = obj.splat1(*args)
88115
after_usage.should == h
89116
after_usage.should_not.equal?(h)
90-
after_usage.should_not.equal?(marked)
91-
Hash.ruby2_keywords_hash?(after_usage).should == false
117+
after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
118+
Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
119+
Hash.ruby2_keywords_hash?(marked).should == true
120+
121+
args = mark(**h)
122+
marked = args.last
123+
after_usage = obj.proc_call(*args)
124+
after_usage.should == h
125+
after_usage.should_not.equal?(h)
126+
after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625
127+
Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625
128+
Hash.ruby2_keywords_hash?(marked).should == true
129+
130+
args = mark(**h)
131+
marked = args.last
132+
after_usage = obj.send(:splat, *args)
133+
after_usage.should == h
134+
after_usage.should_not.equal?(h)
135+
send_copies = RUBY_ENGINE == "ruby" # inconsistent with Proc#call above for CRuby
136+
after_usage.equal?(marked).should == !send_copies
137+
Hash.ruby2_keywords_hash?(after_usage).should == !send_copies
92138
Hash.ruby2_keywords_hash?(marked).should == true
93139
end
94140

spec/ruby/language/keyword_arguments_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,40 @@ def m(*args)
302302
m(a: 1).should == [[{a: 1}], {}]
303303
m({a: 1}).should == [[{a: 1}], {}]
304304
end
305+
306+
# https://bugs.ruby-lang.org/issues/18625
307+
it "works with call(*ruby2_keyword_args) with missing ruby2_keywords in between due to CRuby bug #18625" do
308+
class << self
309+
def n(*args) # Note the missing ruby2_keywords here
310+
target(*args)
311+
end
312+
313+
ruby2_keywords def m(*args)
314+
n(*args)
315+
end
316+
end
317+
318+
empty = {}
319+
m(**empty).should == [[], {}]
320+
Hash.ruby2_keywords_hash?(empty).should == false
321+
m(empty).should == [[{}], {}]
322+
Hash.ruby2_keywords_hash?(empty).should == false
323+
324+
m(a: 1).should == [[], {a: 1}]
325+
m({a: 1}).should == [[{a: 1}], {}]
326+
327+
kw = {a: 1}
328+
329+
m(**kw).should == [[], {a: 1}]
330+
m(**kw)[1].should == kw
331+
m(**kw)[1].should_not.equal?(kw)
332+
Hash.ruby2_keywords_hash?(kw).should == false
333+
Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
334+
335+
m(kw).should == [[{a: 1}], {}]
336+
m(kw)[0][0].should.equal?(kw)
337+
Hash.ruby2_keywords_hash?(kw).should == false
338+
end
305339
end
306340
end
307341
end

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ protected Object callSuper(VirtualFrame frame, Object[] args) {
10931093
final InternalMethod superMethod = superMethodLookup.getMethod();
10941094
// This C API only passes positional arguments, but maybe it should be influenced by ruby2_keywords hashes?
10951095
return callSuperMethodNode.execute(
1096-
frame, callingSelf, superMethod, EmptyArgumentsDescriptor.INSTANCE, args, nil);
1096+
frame, callingSelf, superMethod, EmptyArgumentsDescriptor.INSTANCE, args, nil, null);
10971097
}
10981098

10991099
@TruffleBoundary

src/main/java/org/truffleruby/core/basicobject/BasicObjectNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ protected Object instanceExec(VirtualFrame frame, Object receiver, Object[] argu
448448
block.declarationContext.getRefinements());
449449
var descriptor = RubyArguments.getDescriptor(frame);
450450
return callBlockNode.executeCallBlock(
451-
declarationContext, block, receiver, block.block, descriptor, arguments);
451+
declarationContext, block, receiver, block.block, descriptor, arguments, null);
452452
}
453453

454454
@Specialization

src/main/java/org/truffleruby/core/method/MethodNodes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ protected Object call(Frame callerFrame, RubyMethod method, Object[] rubyArgs, R
138138
final Object[] newArgs = RubyArguments.repack(rubyArgs, method.receiver);
139139
RubyArguments.setMethod(newArgs, internalMethod);
140140
assert RubyArguments.assertFrameArguments(newArgs);
141-
return callInternalMethodNode.execute(callerFrame, internalMethod, method.receiver, newArgs);
141+
return callInternalMethodNode.execute(callerFrame, internalMethod, method.receiver, newArgs, null);
142142
}
143143
}
144144

@@ -360,7 +360,7 @@ public Object execute(VirtualFrame frame) {
360360
final Object originalBoundMethodReceiver = RubyArguments.getSelf(RubyArguments.getDeclarationFrame(frame));
361361
Object[] rubyArgs = RubyArguments.repack(frame.getArguments(), originalBoundMethodReceiver);
362362
RubyArguments.setMethod(rubyArgs, method);
363-
return callInternalMethodNode.execute(frame, method, originalBoundMethodReceiver, rubyArgs);
363+
return callInternalMethodNode.execute(frame, method, originalBoundMethodReceiver, rubyArgs, null);
364364
}
365365
}
366366

src/main/java/org/truffleruby/core/method/RubyMethod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public Object execute(Object[] arguments,
7373
final Object[] convertedArguments = foreignToRubyArgumentsNode.executeConvert(arguments);
7474
final Object[] frameArgs = RubyArguments.pack(null, null, method, null, receiver, nil,
7575
EmptyArgumentsDescriptor.INSTANCE, convertedArguments);
76-
return callInternalMethodNode.execute(null, method, receiver, frameArgs);
76+
return callInternalMethodNode.execute(null, method, receiver, frameArgs, null);
7777
}
7878
// endregion
7979

src/main/java/org/truffleruby/core/module/ModuleNodes.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ public Object classExec(ArgumentsDescriptor descriptor, RubyModule self, Object[
809809
new FixedDefaultDefinee(self),
810810
block.declarationContext.getRefinements());
811811

812-
return callBlockNode.executeCallBlock(declarationContext, block, self, block.block, descriptor, args);
812+
return callBlockNode.executeCallBlock(declarationContext, block, self, block.block, descriptor, args, null);
813813
}
814814
}
815815

@@ -2271,7 +2271,8 @@ protected RubyModule refine(RubyModule namespace, RubyModule moduleToRefine, Rub
22712271
refinement,
22722272
block.block,
22732273
EmptyArgumentsDescriptor.INSTANCE,
2274-
EMPTY_ARGUMENTS);
2274+
EMPTY_ARGUMENTS,
2275+
null);
22752276
return refinement;
22762277
}
22772278

src/main/java/org/truffleruby/core/proc/ProcNodes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ protected Object call(Frame callerFrame, RubyProc proc, Object[] rubyArgs, RootC
204204
ProcOperations.getSelf(proc),
205205
RubyArguments.getBlock(rubyArgs),
206206
RubyArguments.getDescriptor(rubyArgs),
207-
RubyArguments.getRawArguments(rubyArgs));
207+
RubyArguments.getRawArguments(rubyArgs),
208+
null);
208209
}
209210
}
210211

0 commit comments

Comments
 (0)