Skip to content

Commit 79898d8

Browse files
committed
[GR-15171] Kwargs fix.
PullRequest: truffleruby/796
2 parents 50c7c01 + a2678c1 commit 79898d8

File tree

6 files changed

+167
-9
lines changed

6 files changed

+167
-9
lines changed

CHANGELOG.md

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

55
* Fixed `BigDecimal#dup` so it now just returns the receiver, per Ruby 2.5+ semantics (#1680).
66
* Implemented `rb_eval_string_protect`.
7+
* Fixed `rb_get_kwargs` to correctly handle optional and rest arguments.
78

89
# 1.0 RC 17
910

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ VALUE hash_spec_rb_hash_lookup2(VALUE self, VALUE hash, VALUE key, VALUE def) {
9696
return rb_hash_lookup2(hash, key, def);
9797
}
9898

99+
VALUE hash_spec_rb_hash_lookup2_default_undef(VALUE self, VALUE hash, VALUE key) {
100+
VALUE ret = rb_hash_lookup2(hash, key, Qundef);
101+
return ret == Qundef ? Qtrue : Qfalse;
102+
}
103+
99104
VALUE hash_spec_rb_hash_new(VALUE self) {
100105
return rb_hash_new();
101106
}
@@ -127,6 +132,7 @@ void Init_hash_spec(void) {
127132
rb_define_method(cls, "rb_hash_lookup_nil", hash_spec_rb_hash_lookup_nil, 2);
128133
rb_define_method(cls, "rb_hash_lookup", hash_spec_rb_hash_lookup, 2);
129134
rb_define_method(cls, "rb_hash_lookup2", hash_spec_rb_hash_lookup2, 3);
135+
rb_define_method(cls, "rb_hash_lookup2_default_undef", hash_spec_rb_hash_lookup2_default_undef, 2);
130136
rb_define_method(cls, "rb_hash_new", hash_spec_rb_hash_new, 0);
131137
rb_define_method(cls, "rb_hash_size", hash_spec_rb_hash_size, 1);
132138
rb_define_method(cls, "rb_hash_set_ifnone", hash_spec_rb_hash_set_ifnone, 2);

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ VALUE util_spec_rb_scan_args(VALUE self, VALUE argv, VALUE fmt, VALUE expected,
4444
return INT2NUM(result);
4545
}
4646

47+
static VALUE util_spec_rb_get_kwargs(VALUE self, VALUE keyword_hash, VALUE keys, VALUE required, VALUE optional) {
48+
int req = FIX2INT(required);
49+
int opt = FIX2INT(optional);
50+
int len = RARRAY_LEN(keys);
51+
52+
int values_len = req + (opt < 0 ? -1 - opt : opt);
53+
int i = 0;
54+
55+
ID *ids = malloc(sizeof(VALUE) * len);
56+
VALUE *results = malloc(sizeof(VALUE) * values_len);
57+
int extracted = 0;
58+
VALUE ary = Qundef;
59+
60+
for (i = 0; i < len; i++) {
61+
ids[i] = SYM2ID(rb_ary_entry(keys, i));
62+
}
63+
64+
extracted = rb_get_kwargs(keyword_hash, ids, req, opt, results);
65+
ary = rb_ary_new_from_values(extracted, results);
66+
free(results);
67+
free(ids);
68+
return ary;
69+
}
70+
4771
static VALUE util_spec_rb_long2int(VALUE self, VALUE n) {
4872
return INT2NUM(rb_long2int(NUM2LONG(n)));
4973
}
@@ -64,6 +88,7 @@ static VALUE util_spec_rb_sourceline(VALUE self) {
6488
void Init_util_spec(void) {
6589
VALUE cls = rb_define_class("CApiUtilSpecs", rb_cObject);
6690
rb_define_method(cls, "rb_scan_args", util_spec_rb_scan_args, 4);
91+
rb_define_method(cls, "rb_get_kwargs", util_spec_rb_get_kwargs, 4);
6792
rb_define_method(cls, "rb_long2int", util_spec_rb_long2int, 1);
6893
rb_define_method(cls, "rb_iter_break", util_spec_rb_iter_break, 0);
6994
rb_define_method(cls, "rb_sourcefile", util_spec_rb_sourcefile, 0);

spec/ruby/optional/capi/hash_spec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@
211211

212212
@s.rb_hash_lookup2(hash, :chunky, 10).should == 10
213213
end
214+
215+
it "returns undefined if that is the default value specified" do
216+
hsh = Hash.new(0)
217+
@s.rb_hash_lookup2_default_undef(hsh, :chunky).should be_true
218+
end
214219
end
215220
end
216221

spec/ruby/optional/capi/util_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,50 @@
154154
end
155155
end
156156

157+
describe "rb_get_kwargs" do
158+
it "extracts required arguments in the order requested" do
159+
h = { :a => 7, :b => 5 }
160+
@o.rb_get_kwargs(h, [:b, :a], 2, 0).should == [5, 7]
161+
h.should == {}
162+
end
163+
164+
it "extracts required and optional arguments in the order requested" do
165+
h = { :a => 7, :c => 12, :b => 5 }
166+
@o.rb_get_kwargs(h, [:b, :a, :c], 2, 1).should == [5, 7, 12]
167+
h.should == {}
168+
end
169+
170+
it "accepts nil instead of a hash when only optional arguments are requested" do
171+
h = nil
172+
@o.rb_get_kwargs(h, [:b, :a, :c], 0, 3).should == []
173+
h.should == nil
174+
end
175+
176+
it "raises an error if a required argument is not in the hash" do
177+
h = { :a => 7, :c => 12, :b => 5 }
178+
lambda { @o.rb_get_kwargs(h, [:b, :d], 2, 0) }.should raise_error(ArgumentError, /missing keyword: d/)
179+
h.should == {:a => 7, :c => 12}
180+
end
181+
182+
it "does not raise an error for an optional argument not in the hash" do
183+
h = { :a => 7, :b => 5 }
184+
@o.rb_get_kwargs(h, [:b, :a, :c], 2, 1).should == [5, 7]
185+
h.should == {}
186+
end
187+
188+
it "raises an error if there are additional arguments and optional is positive" do
189+
h = { :a => 7, :c => 12, :b => 5 }
190+
lambda { @o.rb_get_kwargs(h, [:b, :a], 2, 0) }.should raise_error(ArgumentError, /unknown keyword: c/)
191+
h.should == {:c => 12}
192+
end
193+
194+
it "leaves additional arguments in the hash if optional is negative" do
195+
h = { :a => 7, :c => 12, :b => 5 }
196+
@o.rb_get_kwargs(h, [:b, :a], 2, -1).should == [5, 7]
197+
h.should == {:c => 12}
198+
end
199+
end
200+
157201
platform_is wordsize: 64 do
158202
describe "rb_long2int" do
159203
it "raises a RangeError if the value is outside the range of a C int" do

src/main/c/cext/ruby.c

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,13 +1655,21 @@ VALUE rb_hash_lookup(VALUE hash, VALUE key) {
16551655
}
16561656

16571657
VALUE rb_hash_lookup2(VALUE hash, VALUE key, VALUE default_value) {
1658-
return RUBY_INVOKE(hash, "fetch", key, default_value);
1658+
VALUE result = RUBY_INVOKE(hash, "_get_or_undefined", key);
1659+
if (result == Qundef) {
1660+
result = default_value;
1661+
}
1662+
return result;
16591663
}
16601664

16611665
VALUE rb_hash_set_ifnone(VALUE hash, VALUE if_none) {
16621666
return RUBY_CEXT_INVOKE("rb_hash_set_ifnone", hash, if_none);
16631667
}
16641668

1669+
VALUE rb_hash_keys(VALUE hash) {
1670+
return RUBY_INVOKE(hash, "keys");
1671+
}
1672+
16651673
VALUE rb_hash_key_str(VALUE hash) {
16661674
rb_tr_error("rb_hash_key_str not yet implemented");
16671675
}
@@ -4678,24 +4686,93 @@ VALUE rb_current_receiver(void) {
46784686
rb_tr_error("rb_current_receiver not implemented");
46794687
}
46804688

4681-
int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values) {
4682-
if (optional == -1) {
4683-
rb_tr_error("rb_get_kwargs with rest not implemented");
4689+
static VALUE rb_keyword_error_new(const char *error, VALUE keys) {
4690+
long i = 0, len = RARRAY_LEN(keys);
4691+
VALUE error_message = rb_sprintf("%s keyword%.*s", error, len > 1, "s");
4692+
4693+
if (len > 0) {
4694+
rb_str_append(error_message, rb_str_new_cstr(": "));
4695+
while (1) {
4696+
const VALUE k = RARRAY_AREF(keys, i);
4697+
Check_Type(k, T_SYMBOL); /* wrong hash is given to rb_get_kwargs */
4698+
rb_str_append(error_message, rb_sym2str(k));
4699+
if (++i >= len) break;
4700+
rb_str_append(error_message, rb_str_new_cstr(", "));
4701+
}
46844702
}
46854703

4686-
if (optional > 0) {
4687-
rb_tr_error("rb_get_kwargs with optional not implemented");
4704+
return rb_exc_new_str(rb_eArgError, error_message);
4705+
}
4706+
4707+
NORETURN(static void rb_keyword_error(const char *error, VALUE keys)) {
4708+
rb_exc_raise(rb_keyword_error_new(error, keys));
4709+
}
4710+
4711+
NORETURN(static void unknown_keyword_error(VALUE hash, const ID *table, int keywords)) {
4712+
int i;
4713+
for (i = 0; i < keywords; i++) {
4714+
VALUE key = table[i];
4715+
rb_hash_delete(hash, key);
46884716
}
4717+
rb_keyword_error("unknown", rb_hash_keys(hash));
4718+
}
4719+
4720+
static VALUE rb_tr_extract_keyword(VALUE keyword_hash, ID key, VALUE *values) {
4721+
VALUE val = rb_hash_lookup2(keyword_hash, key, Qundef);
4722+
if (values) {
4723+
rb_hash_delete(keyword_hash, key);
4724+
}
4725+
return val;
4726+
}
4727+
4728+
int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values) {
4729+
int rest = 0;
4730+
int extracted = 0;
4731+
VALUE missing = Qnil;
46894732

46904733
if (optional < 0) {
4691-
rb_tr_error("rb_get_kwargs with rest and optional not implemented");
4734+
rest = 1;
4735+
optional = -1-optional;
46924736
}
46934737

46944738
for (int n = 0; n < required; n++) {
4695-
values[n] = rb_hash_fetch(keyword_hash, table[n]);
4739+
VALUE val = rb_tr_extract_keyword(keyword_hash, table[n], values);
4740+
if (values) {
4741+
values[n] = val;
4742+
}
4743+
if (val == Qundef) {
4744+
if (NIL_P(missing)) {
4745+
missing = rb_ary_new();
4746+
}
4747+
rb_ary_push(missing, table[n]);
4748+
rb_keyword_error("missing", missing);
4749+
}
4750+
extracted++;
4751+
}
4752+
4753+
if (optional && !NIL_P(keyword_hash)) {
4754+
for (int m = required; m < required + optional; m++) {
4755+
VALUE val = rb_tr_extract_keyword(keyword_hash, table[m], values);
4756+
if (values) {
4757+
values[m] = val;
4758+
}
4759+
if (val != Qundef) {
4760+
extracted++;
4761+
}
4762+
}
4763+
}
4764+
4765+
if (!rest && !NIL_P(keyword_hash)) {
4766+
if (RHASH_SIZE(keyword_hash) > (unsigned int)(values ? 0 : extracted)) {
4767+
unknown_keyword_error(keyword_hash, table, required + optional);
4768+
}
4769+
}
4770+
4771+
for (int i = extracted; i < required + optional; i++) {
4772+
values[i] = Qundef;
46964773
}
46974774

4698-
return required;
4775+
return extracted;
46994776
}
47004777

47014778
VALUE rb_extract_keywords(VALUE *orighash) {

0 commit comments

Comments
 (0)