diff options
Diffstat (limited to 'include/ruby/ruby.h')
-rw-r--r-- | include/ruby/ruby.h | 106 |
1 files changed, 86 insertions, 20 deletions
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 71a1d2e83d..6f7708009a 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1899,6 +1899,11 @@ VALUE rb_funcall_passing_block(VALUE, ID, int, const VALUE*); VALUE rb_funcall_with_block(VALUE, ID, int, const VALUE*, VALUE); VALUE rb_funcall_with_block_kw(VALUE, ID, int, const VALUE*, VALUE, int); int rb_scan_args(int, const VALUE*, const char*, ...); +#define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS 0 +#define RB_SCAN_ARGS_KEYWORDS 1 +#define RB_SCAN_ARGS_EMPTY_KEYWORDS 2 +#define RB_SCAN_ARGS_LAST_HASH_KEYWORDS 3 +int rb_scan_args_kw(int, int, const VALUE*, const char*, ...); VALUE rb_call_super(int, const VALUE*); VALUE rb_call_super_kw(int, const VALUE*, int); VALUE rb_current_receiver(void); @@ -2324,6 +2329,9 @@ unsigned long ruby_strtoul(const char *str, char **endptr, int base); PRINTF_ARGS(int ruby_snprintf(char *str, size_t n, char const *fmt, ...), 3, 4); int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap); +/* -- Remove In 3.0, Only public for rb_scan_args optimized version -- */ +int rb_empty_keyword_given_p(void); + #if defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P) && defined(HAVE_VA_ARGS_MACRO) && defined(__OPTIMIZE__) # define rb_scan_args(argc,argvp,fmt,...) \ __builtin_choose_expr(__builtin_constant_p(fmt), \ @@ -2519,30 +2527,81 @@ rb_scan_args_set(int argc, const VALUE *argv, int i, argi = 0, vari = 0, last_idx = -1; VALUE *var, hash = Qnil, last_hash = 0; const int n_mand = n_lead + n_trail; + int keyword_given = rb_keyword_given_p(); + int empty_keyword_given = 0; + VALUE tmp_buffer = 0; + + if (!keyword_given) { + empty_keyword_given = rb_empty_keyword_given_p(); + } /* capture an option hash - phase 1: pop */ - if (f_hash && n_mand < argc) { - VALUE last = argv[argc - 1]; - - if (RB_NIL_P(last)) { - /* nil is taken as an empty option hash only if it is not - ambiguous; i.e. '*' is not specified and arguments are - given more than sufficient */ - if (!f_var && n_mand + n_opt < argc) - argc--; - } - else { - hash = rb_check_hash_type(last); - if (!RB_NIL_P(hash)) { - VALUE opts = rb_extract_keywords(&hash); - if (!(last_hash = hash)) argc--; - else last_idx = argc - 1; - hash = opts ? opts : Qnil; - } - } + /* Ignore final positional hash if empty keywords given */ + if (argc > 0 && !(f_hash && empty_keyword_given)) { + VALUE last = argv[argc - 1]; + + if (f_hash && n_mand < argc) { + if (keyword_given) { + if (!RB_TYPE_P(last, T_HASH)) { + rb_warn("Keyword flag set when calling rb_scan_args, but last entry is not a hash"); + } + else { + hash = last; + } + } + else if (NIL_P(last)) { + /* For backwards compatibility, nil is taken as an empty + option hash only if it is not ambiguous; i.e. '*' is + not specified and arguments are given more than sufficient. + This will be removed in Ruby 3. */ + if (!f_var && n_mand + n_opt < argc) { + rb_warn("The last argument is nil, treating as empty keywords"); + argc--; + } + } + else { + hash = rb_check_hash_type(last); + } + + /* Ruby 3: Remove if branch, as it will not attempt to split hashes */ + if (!NIL_P(hash)) { + VALUE opts = rb_extract_keywords(&hash); + + if (!(last_hash = hash)) { + if (!keyword_given) { + /* Warn if treating positional as keyword, as in Ruby 3, + this will be an error */ + rb_warn("The last argument is used as the keyword parameter"); + } + argc--; + } + else { + /* Warn if splitting either positional hash to keywords or keywords + to positional hash, as in Ruby 3, no splitting will be done */ + rb_warn("The last argument is split into positional and keyword parameters"); + last_idx = argc - 1; + } + hash = opts ? opts : Qnil; + } + } + else if (f_hash && keyword_given && n_mand == argc) { + /* Warn if treating keywords as positional, as in Ruby 3, this will be an error */ + rb_warn("The keyword argument is passed as the last hash parameter"); + } + } + if (f_hash && n_mand == argc+1 && empty_keyword_given) { + VALUE *ptr = (VALUE *)rb_alloc_tmp_buffer2(&tmp_buffer, argc+1, sizeof(VALUE)); + memcpy(ptr, argv, sizeof(VALUE)*argc); + ptr[argc] = rb_hash_new(); + argc++; + *(&argv) = ptr; + rb_warn("The keyword argument is passed as the last hash parameter"); } - rb_check_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt); + + if (argc < n_mand) { + goto argc_error; + } /* capture leading mandatory arguments */ for (i = n_lead; i-- > 0; ) { @@ -2600,6 +2659,13 @@ rb_scan_args_set(int argc, const VALUE *argv, } } + if (argi < argc) { + argc_error: + if(tmp_buffer) rb_free_tmp_buffer(&tmp_buffer); + rb_error_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt); + } + + if(tmp_buffer) rb_free_tmp_buffer(&tmp_buffer); return argc; } #endif |