Skip to content

Commit b8086af

Browse files
committed
[GR-34365] Implement full Ruby 3 keyword arguments semantics (#2453)
PullRequest: truffleruby/3231
2 parents 7bfe2a5 + ed0b47d commit b8086af

File tree

227 files changed

+1663
-15348
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

227 files changed

+1663
-15348
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ test/mri/tests/cext-c/**/mkmf.log
7171

7272
# Tests
7373
/mri_tests.txt
74-
/test/truffle/ecosystem/blog5/Gemfile.lock
7574
/test/truffle/ecosystem/blog6/Gemfile.lock
7675
/test/truffle/integration/gem-testing
7776
/truffleruby-gem-test-pack

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Bug fixes:
2323

2424
Compatibility:
2525

26+
* Implement full Ruby 3 keyword arguments semantics (#2453, @eregon, @chrisseaton).
2627
* Implement `ruby_native_thread_p` for compatibility (#2556, @aardvark179).
2728
* Add `rb_argv0` for the `tk` gem. (#2556, @aardvark179).
2829
* Implement more correct conversion of array elements by `Array#pack`(#2503, #2504, @aardvark179).

ci.jsonnet

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,8 @@ local composition_environment = utils.add_inclusion_tracking(part_definitions, "
487487
test_builds:
488488
{
489489
"ruby-lint": $.platform.linux + $.cap.gate + $.jdk.v11 + $.use.common + $.env.jvm + $.use.build + $.run.lint + { timelimit: "45:00" },
490-
# Run specs on MRI to make sure new specs are compatible and have the needed version guards
491-
"ruby-test-specs-mri": $.platform.linux + $.cap.gate + $.use.skip_docs + $.use.common + $.run.test_specs_mri + { timelimit: "45:00" },
490+
# Run specs on CRuby to make sure new specs are compatible and have the needed version guards
491+
"ruby-test-specs-on-cruby": $.platform.linux + $.cap.gate + $.use.skip_docs + $.use.common + $.run.test_specs_mri + { timelimit: "45:00" },
492492
} +
493493

494494
{

lib/cext/ABI_check.txt

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

lib/mri/pp.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,7 @@ def seplist(list, sep=nil, iter_method=:each) # :yield: element
237237
else
238238
sep.call
239239
end
240-
if defined?(::TruffleRuby)
241-
yield(*v)
242-
else
243-
yield(*v, **{})
244-
end
240+
yield(*v, **{})
245241
}
246242
end
247243

lib/truffle/pathname.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -991,11 +991,11 @@ def zero?() FileTest.zero?(@path) end
991991

992992
class Pathname # * Dir *
993993
# See <tt>Dir.glob</tt>. Returns or yields Pathname objects.
994-
def Pathname.glob(*args) # :yield: pathname
994+
def Pathname.glob(*args, **kwargs) # :yield: pathname
995995
if block_given?
996-
Dir.glob(*args) { |f| yield self.new(f) }
996+
Dir.glob(*args, **kwargs) { |f| yield self.new(f) }
997997
else
998-
Dir.glob(*args).map { |f| self.new(f) }
998+
Dir.glob(*args, **kwargs).map { |f| self.new(f) }
999999
end
10001000
end
10011001

lib/truffle/truffle/cext.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -832,11 +832,7 @@ def rb_str_encode(str, to, ecflags, ecopts)
832832
opts[:invalid] = :replace
833833
end
834834

835-
if opts.empty?
836-
str.encode(to)
837-
else
838-
str.encode(to, opts)
839-
end
835+
str.encode(to, **opts)
840836
end
841837

842838
def rb_str_conv_enc_opts(str, from, to, ecflags, ecopts)
@@ -879,6 +875,10 @@ def rb_funcallv(recv, meth, argv)
879875
Primitive.send_argv_without_cext_lock(recv, meth, argv, nil)
880876
end
881877

878+
def rb_funcallv_keywords(recv, meth, argv)
879+
Primitive.send_argv_keywords_without_cext_lock(recv, meth, argv, nil)
880+
end
881+
882882
def rb_funcall(recv, meth, n, *args)
883883
Primitive.send_without_cext_lock(recv, meth, args, nil)
884884
end
@@ -1183,6 +1183,15 @@ def rb_class_new_instance(klass, args)
11831183
obj
11841184
end
11851185

1186+
def rb_class_new_instance_kw(klass, args)
1187+
*args, kwargs = args
1188+
kwargs = Truffle::Type.rb_convert_type kwargs, Hash, :to_hash
1189+
1190+
obj = klass.send(:__allocate__)
1191+
obj.send(:initialize, *args, **kwargs)
1192+
obj
1193+
end
1194+
11861195
def rb_f_sprintf(args)
11871196
sprintf(*args)
11881197
end

mx.truffleruby/suite.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@
110110
"truffleruby-gem-test-pack",
111111
"lib/json/java",
112112
"lib/ruby",
113-
"test/truffle/ecosystem/blog5",
114113
"test/truffle/ecosystem/blog6",
115114
"test/truffle/ecosystem/hello-world",
116115
"test/truffle/ecosystem/rails-app",

spec/ruby/core/hash/ruby2_keywords_hash_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@
4040
kw.should == h
4141
end
4242

43+
it "copies instance variables" do
44+
h = {a: 1}
45+
h.instance_variable_set(:@foo, 42)
46+
kw = Hash.ruby2_keywords_hash(h)
47+
kw.instance_variable_get(:@foo).should == 42
48+
end
49+
50+
it "copies the hash internals" do
51+
h = {a: 1}
52+
kw = Hash.ruby2_keywords_hash(h)
53+
h[:a] = 2
54+
kw[:a].should == 1
55+
end
56+
4357
it "raises TypeError for non-Hash" do
4458
-> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError)
4559
end

spec/ruby/core/module/ruby2_keywords_spec.rb

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,83 @@ def foo(*a) a.last end
1515
Hash.ruby2_keywords_hash?(last).should == true
1616
end
1717

18+
it "makes a copy of the hash and only marks the copy as keyword hash" do
19+
obj = Object.new
20+
obj.singleton_class.class_exec do
21+
def regular(*args)
22+
args.last
23+
end
24+
25+
ruby2_keywords def foo(*args)
26+
args.last
27+
end
28+
end
29+
30+
h = {a: 1}
31+
ruby_version_is "3.0" do
32+
obj.regular(**h).should.equal?(h)
33+
end
34+
35+
last = obj.foo(**h)
36+
Hash.ruby2_keywords_hash?(last).should == true
37+
Hash.ruby2_keywords_hash?(h).should == false
38+
39+
last2 = obj.foo(**last) # last is already marked
40+
Hash.ruby2_keywords_hash?(last2).should == true
41+
Hash.ruby2_keywords_hash?(last).should == true
42+
last2.should_not.equal?(last)
43+
Hash.ruby2_keywords_hash?(h).should == false
44+
end
45+
46+
it "makes a copy and unmark at the call site when calling with marked *args" do
47+
obj = Object.new
48+
obj.singleton_class.class_exec do
49+
ruby2_keywords def foo(*args)
50+
args
51+
end
52+
53+
def single(arg)
54+
arg
55+
end
56+
57+
def splat(*args)
58+
args.last
59+
end
60+
61+
def kwargs(**kw)
62+
kw
63+
end
64+
end
65+
66+
h = { a: 1 }
67+
args = obj.foo(**h)
68+
marked = args.last
69+
Hash.ruby2_keywords_hash?(marked).should == true
70+
71+
after_usage = obj.single(*args)
72+
after_usage.should == h
73+
after_usage.should_not.equal?(h)
74+
after_usage.should_not.equal?(marked)
75+
Hash.ruby2_keywords_hash?(after_usage).should == false
76+
Hash.ruby2_keywords_hash?(marked).should == true
77+
78+
after_usage = obj.splat(*args)
79+
after_usage.should == h
80+
after_usage.should_not.equal?(h)
81+
after_usage.should_not.equal?(marked)
82+
ruby_bug "#18625", ""..."3.2" do
83+
Hash.ruby2_keywords_hash?(after_usage).should == false
84+
end
85+
Hash.ruby2_keywords_hash?(marked).should == true
86+
87+
after_usage = obj.kwargs(*args)
88+
after_usage.should == h
89+
after_usage.should_not.equal?(h)
90+
after_usage.should_not.equal?(marked)
91+
Hash.ruby2_keywords_hash?(after_usage).should == false
92+
Hash.ruby2_keywords_hash?(marked).should == true
93+
end
94+
1895
it "applies to the underlying method and applies across aliasing" do
1996
obj = Object.new
2097

@@ -80,7 +157,7 @@ def foo(*a) end
80157
}.should raise_error(NameError, /undefined method `not_existing'/)
81158
end
82159

83-
it "acceps String as well" do
160+
it "accepts String as well" do
84161
obj = Object.new
85162

86163
obj.singleton_class.class_exec do

0 commit comments

Comments
 (0)