Skip to content

Commit c5159ce

Browse files
committed
[GR-19220] Implement rb_scan_args_kw (#2244).
PullRequest: truffleruby/2389
2 parents ce59290 + 86acf00 commit c5159ce

File tree

3 files changed

+78
-34
lines changed

3 files changed

+78
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Compatibility:
4949
* Do not call `File.exist?` in `Dir.glob` as `File.exist?` is often mocked (#2236, @gogainda).
5050
* Coerce the inherit argument to a boolean in `Module#const_defined?` and `Module#const_get` (#2240).
5151
* Refinements take place at `Object#method` and `Module#instance_method` (#2004, @ssnickolay).
52+
* Add support for `rb_scan_args_kw` in C API (#2244, @LillianZ).
5253

5354
Performance:
5455

lib/cext/include/truffleruby/truffleruby.h

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,22 @@ if (polyglot_as_boolean(polyglot_invoke(RUBY_CEXT, "warning?"))) { \
6767
} \
6868
})
6969

70-
#define rb_tr_scan_args_1(ARGC, ARGV, FORMAT, V1) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
71-
#define rb_tr_scan_args_2(ARGC, ARGV, FORMAT, V1, V2) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
72-
#define rb_tr_scan_args_3(ARGC, ARGV, FORMAT, V1, V2, V3) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
73-
#define rb_tr_scan_args_4(ARGC, ARGV, FORMAT, V1, V2, V3, V4) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, NULL, NULL, NULL, NULL, NULL, NULL)
74-
#define rb_tr_scan_args_5(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, NULL, NULL, NULL, NULL, NULL)
75-
#define rb_tr_scan_args_6(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, NULL, NULL, NULL, NULL)
76-
#define rb_tr_scan_args_7(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, NULL, NULL, NULL)
77-
#define rb_tr_scan_args_8(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, NULL, NULL)
78-
#define rb_tr_scan_args_9(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, NULL)
79-
#define rb_tr_scan_args_10(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) rb_tr_scan_args(ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10)
80-
81-
#define SCAN_ARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, NAME, ...) NAME
82-
#define rb_scan_args(ARGC, ARGV, FORMAT, ...) SCAN_ARGS_IMPL(__VA_ARGS__, rb_tr_scan_args_10, rb_tr_scan_args_9, rb_tr_scan_args_8, rb_tr_scan_args_7, rb_tr_scan_args_6, rb_tr_scan_args_5, rb_tr_scan_args_4, rb_tr_scan_args_3, rb_tr_scan_args_2, rb_tr_scan_args_1)(ARGC, ARGV, FORMAT, __VA_ARGS__)
70+
#define rb_tr_scan_args_kw_1(KW_FLAG, ARGC, ARGV, FORMAT, V1) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
71+
#define rb_tr_scan_args_kw_2(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
72+
#define rb_tr_scan_args_kw_3(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
73+
#define rb_tr_scan_args_kw_4(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, NULL, NULL, NULL, NULL, NULL, NULL)
74+
#define rb_tr_scan_args_kw_5(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, NULL, NULL, NULL, NULL, NULL)
75+
#define rb_tr_scan_args_kw_6(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, NULL, NULL, NULL, NULL)
76+
#define rb_tr_scan_args_kw_7(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, NULL, NULL, NULL)
77+
#define rb_tr_scan_args_kw_8(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, NULL, NULL)
78+
#define rb_tr_scan_args_kw_9(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, NULL)
79+
#define rb_tr_scan_args_kw_10(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10) rb_tr_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10)
80+
81+
#define SCAN_ARGS_KW_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, NAME, ...) NAME
82+
#define rb_scan_args_kw(KW_FLAG, ARGC, ARGV, FORMAT, ...) SCAN_ARGS_KW_IMPL(__VA_ARGS__, rb_tr_scan_args_kw_10, rb_tr_scan_args_kw_9, rb_tr_scan_args_kw_8, rb_tr_scan_args_kw_7, rb_tr_scan_args_kw_6, rb_tr_scan_args_kw_5, rb_tr_scan_args_kw_4, rb_tr_scan_args_kw_3, rb_tr_scan_args_kw_2, rb_tr_scan_args_kw_1)(KW_FLAG, ARGC, ARGV, FORMAT, __VA_ARGS__)
83+
84+
#define rb_scan_args(ARGC, ARGV, FORMAT, ...) SCAN_ARGS_KW_IMPL(__VA_ARGS__, rb_tr_scan_args_kw_10, rb_tr_scan_args_kw_9, rb_tr_scan_args_kw_8, rb_tr_scan_args_kw_7, rb_tr_scan_args_kw_6, rb_tr_scan_args_kw_5, rb_tr_scan_args_kw_4, rb_tr_scan_args_kw_3, rb_tr_scan_args_kw_2, rb_tr_scan_args_kw_1)(RB_SCAN_ARGS_PASS_CALLED_KEYWORDS, ARGC, ARGV, FORMAT, __VA_ARGS__)
85+
8386

8487
// Invoking ruby methods.
8588

@@ -199,9 +202,8 @@ static inline char *rb_tr_string_value_cstr(VALUE *value_pointer) {
199202
return RSTRING_PTR(string);
200203
}
201204

202-
ALWAYS_INLINE(static int rb_tr_scan_args(int argc, VALUE *argv, const char *format, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10));
203-
static inline int rb_tr_scan_args(int argc, VALUE *argv, const char *format, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10) {
204-
// Parse the format string
205+
ALWAYS_INLINE(static int rb_tr_scan_args_kw(int kw_flag, int argc, VALUE *argv, const char *format, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10));
206+
static inline int rb_tr_scan_args_kw(int kw_flag, int argc, VALUE *argv, const char *format, VALUE *v1, VALUE *v2, VALUE *v3, VALUE *v4, VALUE *v5, VALUE *v6, VALUE *v7, VALUE *v8, VALUE *v9, VALUE *v10) {
205207

206208
// TODO CS 7-Feb-17 maybe we could inline cache this part?
207209

@@ -213,6 +215,19 @@ static inline int rb_tr_scan_args(int argc, VALUE *argv, const char *format, VAL
213215
bool kwargs;
214216
bool block;
215217

218+
// Interpret kw_flag
219+
220+
int keyword_given = 0;
221+
int empty_keyword_given = 0;
222+
int last_hash_keyword = 0;
223+
224+
switch (kw_flag) {
225+
case RB_SCAN_ARGS_PASS_CALLED_KEYWORDS: break;
226+
case RB_SCAN_ARGS_KEYWORDS: keyword_given = 1; break;
227+
case RB_SCAN_ARGS_EMPTY_KEYWORDS: empty_keyword_given = 1; break;
228+
case RB_SCAN_ARGS_LAST_HASH_KEYWORDS: last_hash_keyword = 1; break;
229+
}
230+
216231
// TODO CS 27-Feb-17 can LLVM constant-fold through isdigit?
217232

218233
if (isdigit(*formatp)) {
@@ -276,26 +291,60 @@ static inline int rb_tr_scan_args(int argc, VALUE *argv, const char *format, VAL
276291
// accepting them will still need to be set to Qnil in such cases.
277292
bool erased_kwargs = false;
278293
bool found_kwargs = false;
294+
VALUE hash = Qnil;
279295

280-
if (kwargs && (n_mand < argc)) {
296+
/* capture an option hash - phase 1: pop */
297+
/* Ignore final positional hash if empty keywords given */
298+
if (argc > 0 && !(kwargs && empty_keyword_given)) {
281299
VALUE last = argv[argc - 1];
282300

283-
if (NIL_P(last)) {
284-
/* nil is taken as an empty option hash only if it is not
285-
ambiguous; i.e. '*' is not specified and arguments are
286-
given more than sufficient */
287-
if (rest || argc <= n_mand + n_opt) {
288-
kwargs = false;
289-
erased_kwargs = true;
301+
if (kwargs && n_mand < argc) {
302+
if (keyword_given) {
303+
if (!RB_TYPE_P(last, T_HASH)) {
304+
rb_warn("Keyword flag set when calling rb_scan_args, but last entry is not a hash");
305+
}
306+
else {
307+
hash = last;
308+
}
290309
}
291-
} else {
292-
if (!polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("test_kwargs", argv[argc - 1], Qfalse))) {
293-
kwargs = false;
294-
erased_kwargs = true;
310+
311+
else if (NIL_P(last)) {
312+
/* For backwards compatibility, nil is taken as an empty
313+
option hash only if it is not ambiguous; i.e. '*' is
314+
not specified and arguments are given more than sufficient.
315+
This will be removed in Ruby 3. */
316+
if (rest || argc <= n_mand + n_opt) {
317+
kwargs = false;
318+
erased_kwargs = true;
319+
rb_warn("The last argument is nil, treating as empty keywords");
320+
}
295321
}
322+
else {
323+
hash = rb_check_hash_type(last);
324+
if (NIL_P(hash)) {
325+
kwargs = false;
326+
erased_kwargs = true;
327+
}
328+
}
329+
330+
/* Ruby 3: Remove if branch, as it will not attempt to split hashes */
331+
if (!NIL_P(hash)) {
332+
if (!polyglot_as_boolean(RUBY_CEXT_INVOKE_NO_WRAP("test_kwargs", argv[argc - 1], Qfalse))) {
333+
// Does not handle the case where "The last argument is split into positional and keyword parameters"
334+
// Instead assumes that it is all one hash
335+
kwargs = false;
336+
erased_kwargs = true;
337+
}
338+
}
339+
}
340+
else if (kwargs && keyword_given && n_mand == argc) {
341+
/* Warn if treating keywords as positional, as in Ruby 3, this will be an error */
342+
rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
296343
}
297344
}
298345

346+
// Skipped the part of MRI where empty_keyword_given with rb_warn("Passing the keyword argument as the last hash parameter is deprecated");
347+
299348
int trailing = post;
300349

301350
if (kwargs) {

spec/tags/optional/capi/util_tags.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)