diff --git a/class.c b/class.c index 6b3b6623f1d071..645d78ae8e727b 100644 --- a/class.c +++ b/class.c @@ -1954,7 +1954,30 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) int f_var = 0, f_hash = 0, f_block = 0; int n_lead = 0, n_opt = 0, n_trail = 0, n_mand; int argi = 0, last_idx = -1; + int f_kw = 0; + int keyword_given = 0; + int empty_keyword_given = 0; + int allow_hash_to_keyword = 0; VALUE hash = Qnil, last_hash = 0; + VALUE tmp_buffer = 0; + + if (*p == 'k') { + keyword_given = 1; + p++; + } + else if (*p == 'e') { + empty_keyword_given = 1; + p++; + } + else if (*p == 'n') { + allow_hash_to_keyword = 1; + p++; + } + else { + if(!(keyword_given = rb_keyword_given_p())) { + empty_keyword_given = rb_empty_keyword_given_p(); + } + } if (ISDIGIT(*p)) { n_lead = *p - '0'; @@ -1974,6 +1997,9 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) } if (*p == ':') { f_hash = 1; + /* Optional or variable arguments turn on keyword argument mode */ + /* If all arguments are mandatory, operate in regular argument mode */ + f_kw = f_var || n_opt; p++; } if (*p == '&') { @@ -1985,32 +2011,79 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) } n_mand = n_lead + n_trail; - if (argc < n_mand) - goto argc_error; - va_start(vargs, fmt); /* capture an option hash - phase 1: pop */ - if (f_hash && n_mand < argc) { - VALUE last = argv[argc - 1]; - - if (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 (!NIL_P(hash)) { - VALUE opts = rb_extract_keywords(&hash); - if (!(last_hash = hash)) argc--; - else last_idx = argc - 1; - hash = opts ? opts : Qnil; - } - } + /* In keyword arugment mode, ignore final positional hash if empty keywords given */ + if (argc > 0 && !(f_kw && empty_keyword_given)) { + VALUE last = argv[argc - 1]; + + /* Ruby 3: if (f_hash && (f_kw && keyword_given) || n_mand < argc)) { */ + if (f_hash && n_mand < argc) { + if (f_kw && 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 (f_kw && !keyword_given && !allow_hash_to_keyword) { + /* Warn in keyword argument mode 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 in keyword argument mode 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_kw && keyword_given && n_mand == argc) { + /* Warn in keyword argument mode 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 = 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"); + } + + if (argc < n_mand) { + va_end(vargs); + goto argc_error; + } + /* capture leading mandatory arguments */ for (i = n_lead; i-- > 0; ) { var = va_arg(vargs, VALUE *); @@ -2070,9 +2143,11 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) 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; } diff --git a/error.c b/error.c index 87357a4ebe065e..bef509001170a4 100644 --- a/error.c +++ b/error.c @@ -341,7 +341,7 @@ rb_warn_m(int argc, VALUE *argv, VALUE exc) VALUE opts, location = Qnil; if (!NIL_P(ruby_verbose) && argc > 0 && - (argc = rb_scan_args(argc, argv, "*:", NULL, &opts)) > 0) { + (argc = rb_scan_args(argc, argv, "*:", NULL, &opts)) > 0) { VALUE str = argv[0], uplevel = Qnil; if (!NIL_P(opts)) { static ID kwds[1]; @@ -1595,7 +1595,7 @@ nometh_err_initialize(int argc, VALUE *argv, VALUE self) priv = (argc > 3) && (--argc, RTEST(argv[argc])); args = (argc > 2) ? argv[--argc] : Qnil; if (!NIL_P(options)) argv[argc++] = options; - rb_call_super(argc, argv); + rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); return nometh_err_init_attr(self, args, priv); } diff --git a/ext/-test-/scan_args/scan_args.c b/ext/-test-/scan_args/scan_args.c index dca353f6436308..7593e57b7150f4 100644 --- a/ext/-test-/scan_args/scan_args.c +++ b/ext/-test-/scan_args/scan_args.c @@ -250,6 +250,33 @@ scan_args_lead_opt_var_trail_hash(int argc, VALUE *argv, VALUE self) return rb_ary_new_from_values(numberof(args), args); } +static VALUE +scan_args_k_lead_opt_hash(int argc, VALUE *argv, VALUE self) +{ + VALUE args[4]; + int n = rb_scan_args(argc, argv, "k11:", args+1, args+2, args+3); + args[0] = INT2NUM(n); + return rb_ary_new_from_values(numberof(args), args); +} + +static VALUE +scan_args_e_lead_opt_hash(int argc, VALUE *argv, VALUE self) +{ + VALUE args[4]; + int n = rb_scan_args(argc, argv, "e11:", args+1, args+2, args+3); + args[0] = INT2NUM(n); + return rb_ary_new_from_values(numberof(args), args); +} + +static VALUE +scan_args_n_lead_opt_hash(int argc, VALUE *argv, VALUE self) +{ + VALUE args[4]; + int n = rb_scan_args(argc, argv, "n11:", args+1, args+2, args+3); + args[0] = INT2NUM(n); + return rb_ary_new_from_values(numberof(args), args); +} + void Init_scan_args(void) { @@ -282,5 +309,7 @@ Init_scan_args(void) rb_define_singleton_method(module, "lead_var_trail_hash", scan_args_lead_var_trail_hash, -1); rb_define_singleton_method(module, "opt_var_trail_hash", scan_args_opt_var_trail_hash, -1); rb_define_singleton_method(module, "lead_opt_var_trail_hash", scan_args_lead_opt_var_trail_hash, -1); + rb_define_singleton_method(module, "k_lead_opt_hash", scan_args_k_lead_opt_hash, -1); + rb_define_singleton_method(module, "e_lead_opt_hash", scan_args_e_lead_opt_hash, -1); + rb_define_singleton_method(module, "n_lead_opt_hash", scan_args_n_lead_opt_hash, -1); } - diff --git a/ext/pathname/pathname.c b/ext/pathname/pathname.c index 70f82583a1b42b..52ddda1f8fec81 100644 --- a/ext/pathname/pathname.c +++ b/ext/pathname/pathname.c @@ -393,7 +393,7 @@ path_read(int argc, VALUE *argv, VALUE self) args[0] = get_strpath(self); n = rb_scan_args(argc, argv, "03", &args[1], &args[2], &args[3]); - return rb_funcallv(rb_cFile, id_read, 1+n, args); + return rb_funcallv_kw(rb_cFile, id_read, 1+n, args, RB_PASS_CALLED_KEYWORDS); } /* @@ -1097,12 +1097,12 @@ path_s_glob(int argc, VALUE *argv, VALUE klass) n = rb_scan_args(argc, argv, "12", &args[0], &args[1], &args[2]); if (rb_block_given_p()) { - return rb_block_call(rb_cDir, id_glob, n, args, s_glob_i, klass); + return rb_block_call_kw(rb_cDir, id_glob, n, args, s_glob_i, klass, RB_PASS_CALLED_KEYWORDS); } else { VALUE ary; long i; - ary = rb_funcallv(rb_cDir, id_glob, n, args); + ary = rb_funcallv_kw(rb_cDir, id_glob, n, args, RB_PASS_CALLED_KEYWORDS); ary = rb_convert_type(ary, T_ARRAY, "Array", "to_ary"); for (i = 0; i < RARRAY_LEN(ary); i++) { VALUE elt = RARRAY_AREF(ary, i); @@ -1145,12 +1145,12 @@ path_glob(int argc, VALUE *argv, VALUE self) n = 3; if (rb_block_given_p()) { - return rb_block_call(rb_cDir, id_glob, n, args, glob_i, self); + return rb_block_call_kw(rb_cDir, id_glob, n, args, glob_i, self, RB_PASS_KEYWORDS); } else { VALUE ary; long i; - ary = rb_funcallv(rb_cDir, id_glob, n, args); + ary = rb_funcallv_kw(rb_cDir, id_glob, n, args, RB_PASS_KEYWORDS); ary = rb_convert_type(ary, T_ARRAY, "Array", "to_ary"); for (i = 0; i < RARRAY_LEN(ary); i++) { VALUE elt = RARRAY_AREF(ary, i); diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 4a81e8418308ea..ab533a03fb0f7d 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -385,7 +385,7 @@ strio_finalize(VALUE self) static VALUE strio_s_open(int argc, VALUE *argv, VALUE klass) { - VALUE obj = rb_class_new_instance(argc, argv, klass); + VALUE obj = rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS); if (!rb_block_given_p()) return obj; return rb_ensure(rb_yield, obj, strio_finalize, obj); } @@ -400,7 +400,7 @@ strio_s_new(int argc, VALUE *argv, VALUE klass) rb_warn("%"PRIsVALUE"::new() does not take block; use %"PRIsVALUE"::open() instead", cname, cname); } - return rb_class_new_instance(argc, argv, klass); + return rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS); } /* diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index c438b0aea724f4..258df8d60f3826 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -2325,6 +2325,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), \ @@ -2374,12 +2377,17 @@ ERRORFUNC(("variable argument length doesn't match"), void rb_scan_args_length_m rb_scan_args_count_var(fmt, ofs, vari) : \ rb_scan_args_count_var(fmt, ofs+1, vari+fmt[ofs]-'0')) +# define rb_scan_kw_p(c) ((c) == 'k' || (c) == 'e' || (c) == 'n') + # define rb_scan_args_count_lead(fmt, ofs, vari) \ (!rb_scan_args_isdigit(fmt[ofs]) ? \ rb_scan_args_count_var(fmt, ofs, vari) : \ rb_scan_args_count_opt(fmt, ofs+1, vari+fmt[ofs]-'0')) -# define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0) +# define rb_scan_args_count(fmt) \ + (rb_scan_kw_p(fmt[0]) ? \ + rb_scan_args_count_lead((fmt+1), 0, 0) : \ + rb_scan_args_count_lead((fmt)), 0, 0) # if defined(__has_attribute) && __has_attribute(diagnose_if) # define rb_scan_args_verify(fmt, varc) (void)0 @@ -2392,39 +2400,75 @@ ERRORFUNC(("variable argument length doesn't match"), void rb_scan_args_length_m (void)0) # endif +ALWAYS_INLINE(static int rb_scan_args_kw_p(const char *fmt)); +static inline int +rb_scan_args_kw_p(const char *fmt) +{ + switch (fmt[0]) { + case 'k': + case 'e': + case 'n': + return 1; + } + return 0; +} + +ALWAYS_INLINE(static int rb_scan_args_kw(const char *fmt)); +static inline int +rb_scan_args_kw(const char *fmt) +{ + switch (fmt[0]) { + case 'k': + return 1; + case 'e': + return 2; + case 'n': + return 3; + } + return 0; +} + +ALWAYS_INLINE(static int rb_scan_args_n_lead_idx(const char *fmt)); +static inline int +rb_scan_args_n_lead_idx(const char *fmt) +{ + return rb_scan_args_kw_p(fmt); +} + ALWAYS_INLINE(static int rb_scan_args_lead_p(const char *fmt)); static inline int rb_scan_args_lead_p(const char *fmt) { - return rb_scan_args_isdigit(fmt[0]); + return rb_scan_args_isdigit(fmt[rb_scan_args_n_lead_idx(fmt)]); } ALWAYS_INLINE(static int rb_scan_args_n_lead(const char *fmt)); static inline int rb_scan_args_n_lead(const char *fmt) { - return (rb_scan_args_lead_p(fmt) ? fmt[0]-'0' : 0); + return (rb_scan_args_lead_p(fmt) ? fmt[rb_scan_args_n_lead_idx(fmt)]-'0' : 0); } ALWAYS_INLINE(static int rb_scan_args_opt_p(const char *fmt)); static inline int rb_scan_args_opt_p(const char *fmt) { - return (rb_scan_args_lead_p(fmt) && rb_scan_args_isdigit(fmt[1])); + return (rb_scan_args_lead_p(fmt) && rb_scan_args_isdigit(fmt[rb_scan_args_n_lead_idx(fmt)+1])); } ALWAYS_INLINE(static int rb_scan_args_n_opt(const char *fmt)); static inline int rb_scan_args_n_opt(const char *fmt) { - return (rb_scan_args_opt_p(fmt) ? fmt[1]-'0' : 0); + return (rb_scan_args_opt_p(fmt) ? fmt[rb_scan_args_n_lead_idx(fmt)+1]-'0' : 0); } ALWAYS_INLINE(static int rb_scan_args_var_idx(const char *fmt)); static inline int rb_scan_args_var_idx(const char *fmt) { - return (!rb_scan_args_lead_p(fmt) ? 0 : !rb_scan_args_isdigit(fmt[1]) ? 1 : 2); + const int idx = rb_scan_args_n_lead_idx(fmt); + return idx + (!rb_scan_args_lead_p(fmt) ? 0 : !rb_scan_args_isdigit(fmt[idx+1]) ? 1 : 2); } ALWAYS_INLINE(static int rb_scan_args_f_var(const char *fmt)); @@ -2494,6 +2538,7 @@ rb_scan_args_end_idx(const char *fmt) /* https://bugs.llvm.org/show_bug.cgi?id=38095 */ # define rb_scan_args0(argc, argv, fmt, varc, vars) \ rb_scan_args_set(argc, argv, \ + rb_scan_args_kw(fmt), \ rb_scan_args_n_lead(fmt), \ rb_scan_args_n_opt(fmt), \ rb_scan_args_n_trail(fmt), \ @@ -2502,13 +2547,13 @@ rb_scan_args_end_idx(const char *fmt) rb_scan_args_f_block(fmt), \ (rb_scan_args_verify(fmt, varc), vars), (char *)fmt, varc) ALWAYS_INLINE(static int -rb_scan_args_set(int argc, const VALUE *argv, +rb_scan_args_set(int argc, const VALUE *argv, int kw_flag, int n_lead, int n_opt, int n_trail, int f_var, int f_hash, int f_block, VALUE *vars[], char *fmt, int varc)); inline int -rb_scan_args_set(int argc, const VALUE *argv, +rb_scan_args_set(int argc, const VALUE *argv, int kw_flag, int n_lead, int n_opt, int n_trail, int f_var, int f_hash, int f_block, VALUE *vars[], RB_UNUSED_VAR(char *fmt), RB_UNUSED_VAR(int varc)) @@ -2520,30 +2565,99 @@ 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; + const int f_kw = (f_hash && (f_var || n_opt)); + int keyword_given = 0; + int empty_keyword_given = 0; + int allow_hash_to_keyword = 0; + VALUE tmp_buffer = 0; + + switch (kw_flag) { + case 0: + if (!(keyword_given = rb_keyword_given_p())) { + empty_keyword_given = rb_empty_keyword_given_p(); + } + break; + case 1: + keyword_given = 1; + break; + case 2: + empty_keyword_given = 1; + break; + case 3: + allow_hash_to_keyword = 1; + break; + } /* 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; - } - } + /* In keyword arugment mode, ignore final positional hash if empty keywords given */ + if (argc > 0 && !(f_kw && empty_keyword_given)) { + VALUE last = argv[argc - 1]; + + /* Ruby 3: if (f_hash && (f_kw && keyword_given) || n_mand < argc)) { */ + if (f_hash && n_mand < argc) { + if (f_kw && 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 (f_kw && !keyword_given && !allow_hash_to_keyword) { + /* Warn in keyword argument mode 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 in keyword argument mode 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_kw && keyword_given && n_mand == argc) { + /* Warn in keyword argument mode 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; ) { @@ -2601,6 +2715,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 diff --git a/internal.h b/internal.h index 3dbe9ad38e6316..76cf49c5d19f8f 100644 --- a/internal.h +++ b/internal.h @@ -1552,9 +1552,6 @@ void rb_class_modify_check(VALUE); #define id_status ruby_static_id_status NORETURN(VALUE rb_f_raise(int argc, VALUE *argv)); -/* -- Remove In 3.0 -- */ -int rb_empty_keyword_given_p(void); - /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); diff --git a/io.c b/io.c index 2520725814df3d..ffa484f0b82843 100644 --- a/io.c +++ b/io.c @@ -7022,7 +7022,7 @@ rb_open_file(int argc, const VALUE *argv, VALUE io) static VALUE rb_io_s_open(int argc, VALUE *argv, VALUE klass) { - VALUE io = rb_class_new_instance(argc, argv, klass); + VALUE io = rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); @@ -7209,7 +7209,7 @@ rb_f_open(int argc, VALUE *argv, VALUE _) } } if (redirect) { - VALUE io = rb_funcallv(argv[0], to_open, argc-1, argv+1); + VALUE io = rb_funcallv_kw(argv[0], to_open, argc-1, argv+1, RB_PASS_CALLED_KEYWORDS); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); @@ -8390,7 +8390,7 @@ rb_io_s_new(int argc, VALUE *argv, VALUE klass) rb_warn("%"PRIsVALUE"::new() does not take block; use %"PRIsVALUE"::open() instead", cname, cname); } - return rb_class_new_instance(argc, argv, klass); + return rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS); } @@ -10373,7 +10373,7 @@ open_key_args(VALUE klass, int argc, VALUE *argv, VALUE opt, struct foreach_arg v = rb_to_array_type(v); n = RARRAY_LENINT(v); rb_check_arity(n, 0, 3); /* rb_io_open */ - rb_scan_args(n, RARRAY_CONST_PTR(v), "02:", &vmode, &vperm, &opt); + rb_scan_args(n, RARRAY_CONST_PTR(v), "n02:", &vmode, &vperm, &opt); } arg->io = rb_io_open(klass, path, vmode, vperm, opt); } diff --git a/lib/csv.rb b/lib/csv.rb index 61702137d0bf87..60dbbcc23008e9 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -637,7 +637,7 @@ def self.open(filename, mode="r", **options) file_opts = {universal_newline: false}.merge(options) begin - f = File.open(filename, mode, file_opts) + f = File.open(filename, mode, **file_opts) rescue ArgumentError => e raise unless /needs binmode/.match?(e.message) and mode == "r" mode = "rb" diff --git a/lib/open-uri.rb b/lib/open-uri.rb index d9517e4b9e3b33..f4cb53600688fd 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -10,15 +10,15 @@ class << self alias open_uri_original_open open # :nodoc: end - def open(name, *rest, &block) # :nodoc: + def open(name, *rest, **kw, &block) # :nodoc: if (name.respond_to?(:open) && !name.respond_to?(:to_path)) || (name.respond_to?(:to_str) && %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name && (uri = URI.parse(name)).respond_to?(:open)) warn('calling URI.open via Kernel#open is deprecated, call URI.open directly', uplevel: 1) - URI.open(name, *rest, &block) + URI.open(name, *rest, **kw, &block) else - open_uri_original_open(name, *rest, &block) + open_uri_original_open(name, *rest, **kw, &block) end end module_function :open diff --git a/lib/pstore.rb b/lib/pstore.rb index 4daa2e003f4dff..1180fd50a0a584 100644 --- a/lib/pstore.rb +++ b/lib/pstore.rb @@ -374,7 +374,7 @@ def transaction(read_only = false) # :yields: pstore def open_and_lock_file(filename, read_only) if read_only begin - file = File.new(filename, RD_ACCESS) + file = File.new(filename, **RD_ACCESS) begin file.flock(File::LOCK_SH) return file @@ -386,7 +386,7 @@ def open_and_lock_file(filename, read_only) return nil end else - file = File.new(filename, RDWR_ACCESS) + file = File.new(filename, **RDWR_ACCESS) file.flock(File::LOCK_EX) return file end @@ -449,7 +449,7 @@ def save_data(original_checksum, original_file_size, file) def save_data_with_atomic_file_rename_strategy(data, file) temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}" - temp_file = File.new(temp_filename, WR_ACCESS) + temp_file = File.new(temp_filename, **WR_ACCESS) begin temp_file.flock(File::LOCK_EX) temp_file.write(data) diff --git a/lib/rubygems/core_ext/kernel_warn.rb b/lib/rubygems/core_ext/kernel_warn.rb index 3e531441edc385..6c5b387e88c088 100755 --- a/lib/rubygems/core_ext/kernel_warn.rb +++ b/lib/rubygems/core_ext/kernel_warn.rb @@ -12,9 +12,9 @@ module Kernel original_warn = method(:warn) - module_function define_method(:warn) {|*messages, uplevel: nil| - unless uplevel - return original_warn.call(*messages) + module_function define_method(:warn) {|*messages, **kw| + unless uplevel = kw[:uplevel] + return original_warn.call(*messages, **kw) end # Ensure `uplevel` fits a `long` @@ -39,7 +39,8 @@ module Kernel end uplevel = start end - original_warn.call(*messages, uplevel: uplevel) + kw[:uplevel] = uplevel + original_warn.call(*messages, **kw) } end end diff --git a/lib/tempfile.rb b/lib/tempfile.rb index 2bd124abeaa4f7..f7caf65f0cb000 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -130,7 +130,7 @@ def initialize(basename="", tmpdir=nil, mode: 0, **options) @mode = mode|File::RDWR|File::CREAT|File::EXCL ::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| opts[:perm] = 0600 - @tmpfile = File.open(tmpname, @mode, opts) + @tmpfile = File.open(tmpname, @mode, **opts) @opts = opts.freeze end ObjectSpace.define_finalizer(self, Remover.new(@tmpfile)) @@ -142,7 +142,7 @@ def initialize(basename="", tmpdir=nil, mode: 0, **options) def open _close mode = @mode & ~(File::CREAT|File::EXCL) - @tmpfile = File.open(@tmpfile.path, mode, @opts) + @tmpfile = File.open(@tmpfile.path, mode, **@opts) __setobj__(@tmpfile) end @@ -329,7 +329,7 @@ def Tempfile.create(basename="", tmpdir=nil, mode: 0, **options) Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| mode |= File::RDWR|File::CREAT|File::EXCL opts[:perm] = 0600 - tmpfile = File.open(tmpname, mode, opts) + tmpfile = File.open(tmpname, mode, **opts) end if block_given? begin diff --git a/rational.c b/rational.c index 0dfc8abe120954..bd71c215eeb846 100644 --- a/rational.c +++ b/rational.c @@ -1543,7 +1543,7 @@ nurat_round_n(int argc, VALUE *argv, VALUE self) { VALUE opt; enum ruby_num_rounding_mode mode = ( - argc = rb_scan_args(argc, argv, "*:", NULL, &opt), + argc = rb_scan_args(argc, argv, "*:", NULL, &opt), rb_num_get_rounding_option(opt)); VALUE (*round_func)(VALUE) = ROUND_FUNC(mode, nurat_round); return f_round_common(argc, argv, self, round_func); diff --git a/test/-ext-/test_scan_args.rb b/test/-ext-/test_scan_args.rb index cb2dab57603274..7150ce25b7d8fe 100644 --- a/test/-ext-/test_scan_args.rb +++ b/test/-ext-/test_scan_args.rb @@ -94,6 +94,13 @@ def test_lead_hash assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash("a", "b")} assert_equal([1, "a", {b: 1}], Bug::ScanArgs.lead_hash("a", b: 1)) assert_equal([1, {b: 1}, nil], Bug::ScanArgs.lead_hash(b: 1)) + assert_equal([1, {"a"=>0, b: 1}, nil], Bug::ScanArgs.lead_hash({"a"=>0, b: 1}, **{})) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_raise(ArgumentError) {Bug::ScanArgs.lead_hash(1, {"a"=>0, b: 1}, **{})} + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {}, nil], Bug::ScanArgs.lead_hash(**{})) + end end def test_opt_hash @@ -102,7 +109,10 @@ def test_opt_hash assert_equal([0, nil, {b: 1}], Bug::ScanArgs.opt_hash(b: 1)) assert_equal([1, "a", {b: 1}], Bug::ScanArgs.opt_hash("a", b: 1)) assert_raise(ArgumentError) {Bug::ScanArgs.opt_hash("a", "b")} - assert_equal([1, {"a"=>0}, {b: 1}], Bug::ScanArgs.opt_hash("a"=>0, b: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([1, {"a"=>0}, {b: 1}], Bug::ScanArgs.opt_hash("a"=>0, b: 1)) + end + assert_equal([1, {"a"=>0, b: 1}, nil], Bug::ScanArgs.opt_hash({"a"=>0, b: 1}, **{})) end def test_lead_opt_hash @@ -110,9 +120,13 @@ def test_lead_opt_hash assert_equal([2, "a", "b", nil], Bug::ScanArgs.lead_opt_hash("a", "b")) assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.lead_opt_hash("a", c: 1)) assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.lead_opt_hash("a", "b", c: 1)) - assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.lead_opt_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.lead_opt_hash(c: 1)) + end assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_hash("a", "b", "c")} - assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_hash("a", "b"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_hash("a", "b"=>0, c: 1)) + end end def test_var_hash @@ -120,7 +134,9 @@ def test_var_hash assert_equal([1, ["a"], nil], Bug::ScanArgs.var_hash("a")) assert_equal([1, ["a"], {b: 1}], Bug::ScanArgs.var_hash("a", b: 1)) assert_equal([0, [], {b: 1}], Bug::ScanArgs.var_hash(b: 1)) - assert_equal([1, [{"a"=>0}], {b: 1}], Bug::ScanArgs.var_hash("a"=>0, b: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([1, [{"a"=>0}], {b: 1}], Bug::ScanArgs.var_hash("a"=>0, b: 1)) + end end def test_lead_var_hash @@ -129,9 +145,13 @@ def test_lead_var_hash assert_equal([2, "a", ["b"], nil], Bug::ScanArgs.lead_var_hash("a", "b")) assert_equal([2, "a", ["b"], {c: 1}], Bug::ScanArgs.lead_var_hash("a", "b", c: 1)) assert_equal([1, "a", [], {c: 1}], Bug::ScanArgs.lead_var_hash("a", c: 1)) - assert_equal([1, {c: 1}, [], nil], Bug::ScanArgs.lead_var_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {c: 1}, [], nil], Bug::ScanArgs.lead_var_hash(c: 1)) + end assert_equal([3, "a", ["b", "c"], nil], Bug::ScanArgs.lead_var_hash("a", "b", "c")) - assert_equal([2, "a", [{"b"=>0}], {c: 1}], Bug::ScanArgs.lead_var_hash("a", "b"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", [{"b"=>0}], {c: 1}], Bug::ScanArgs.lead_var_hash("a", "b"=>0, c: 1)) + end end def test_opt_var_hash @@ -142,7 +162,9 @@ def test_opt_var_hash assert_equal([1, "a", [], {c: 1}], Bug::ScanArgs.opt_var_hash("a", c: 1)) assert_equal([0, nil, [], {c: 1}], Bug::ScanArgs.opt_var_hash(c: 1)) assert_equal([3, "a", ["b", "c"], nil], Bug::ScanArgs.opt_var_hash("a", "b", "c")) - assert_equal([2, "a", [{"b"=>0}], {c: 1}], Bug::ScanArgs.opt_var_hash("a", "b"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", [{"b"=>0}], {c: 1}], Bug::ScanArgs.opt_var_hash("a", "b"=>0, c: 1)) + end end def test_lead_opt_var_hash @@ -151,10 +173,14 @@ def test_lead_opt_var_hash assert_equal([2, "a", "b", [], nil], Bug::ScanArgs.lead_opt_var_hash("a", "b")) assert_equal([2, "a", "b", [], {c: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", c: 1)) assert_equal([1, "a", nil, [], {c: 1}], Bug::ScanArgs.lead_opt_var_hash("a", c: 1)) - assert_equal([1, {c: 1}, nil, [], nil], Bug::ScanArgs.lead_opt_var_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {c: 1}, nil, [], nil], Bug::ScanArgs.lead_opt_var_hash(c: 1)) + end assert_equal([3, "a", "b", ["c"], nil], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c")) assert_equal([3, "a", "b", ["c"], {d: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c", d: 1)) - assert_equal([3, "a", "b", [{"c"=>0}], {d: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c"=>0, d: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([3, "a", "b", [{"c"=>0}], {d: 1}], Bug::ScanArgs.lead_opt_var_hash("a", "b", "c"=>0, d: 1)) + end end def test_opt_trail_hash @@ -163,9 +189,13 @@ def test_opt_trail_hash assert_equal([2, "a", "b", nil], Bug::ScanArgs.opt_trail_hash("a", "b")) assert_equal([1, nil, "a", {c: 1}], Bug::ScanArgs.opt_trail_hash("a", c: 1)) assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.opt_trail_hash("a", "b", c: 1)) - assert_equal([1, nil, {c: 1}, nil], Bug::ScanArgs.opt_trail_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, nil, {c: 1}, nil], Bug::ScanArgs.opt_trail_hash(c: 1)) + end assert_raise(ArgumentError) {Bug::ScanArgs.opt_trail_hash("a", "b", "c")} - assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.opt_trail_hash("a", "b"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.opt_trail_hash("a", "b"=>0, c: 1)) + end end def test_lead_opt_trail_hash @@ -173,12 +203,16 @@ def test_lead_opt_trail_hash assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash("a")} assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash(c: 1)} assert_equal([2, "a", nil, "b", nil], Bug::ScanArgs.lead_opt_trail_hash("a", "b")) - assert_equal([2, "a", nil, {c: 1}, nil], Bug::ScanArgs.lead_opt_trail_hash("a", c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([2, "a", nil, {c: 1}, nil], Bug::ScanArgs.lead_opt_trail_hash("a", c: 1)) + end assert_equal([2, "a", nil, "b", {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", c: 1)) assert_equal([3, "a", "b", "c", nil], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c")) assert_equal([3, "a", "b", "c", {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c", c: 1)) assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c", "d")} - assert_equal([3, "a", "b", {"c"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([3, "a", "b", {"c"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_trail_hash("a", "b", "c"=>0, c: 1)) + end end def test_var_trail_hash @@ -187,45 +221,104 @@ def test_var_trail_hash assert_equal([2, ["a"], "b", nil], Bug::ScanArgs.var_trail_hash("a", "b")) assert_equal([1, [], "a", {c: 1}], Bug::ScanArgs.var_trail_hash("a", c: 1)) assert_equal([2, ["a"], "b", {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", c: 1)) - assert_equal([1, [], {c: 1}, nil], Bug::ScanArgs.var_trail_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, [], {c: 1}, nil], Bug::ScanArgs.var_trail_hash(c: 1)) + end assert_equal([3, ["a", "b"], "c", nil], Bug::ScanArgs.var_trail_hash("a", "b", "c")) assert_equal([3, ["a", "b"], "c", {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", "c", c: 1)) - assert_equal([3, ["a", "b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", "c"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([3, ["a", "b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.var_trail_hash("a", "b", "c"=>0, c: 1)) + end end def test_lead_var_trail_hash assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash()} assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash("a")} assert_raise(ArgumentError) {Bug::ScanArgs.lead_var_trail_hash(c: 1)} - assert_equal([2, "a", [], {c: 1}, nil], Bug::ScanArgs.lead_var_trail_hash("a", c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([2, "a", [], {c: 1}, nil], Bug::ScanArgs.lead_var_trail_hash("a", c: 1)) + end assert_equal([2, "a", [], "b", nil], Bug::ScanArgs.lead_var_trail_hash("a", "b")) assert_equal([2, "a", [], "b", {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", c: 1)) assert_equal([3, "a", ["b"], "c", nil], Bug::ScanArgs.lead_var_trail_hash("a", "b", "c")) assert_equal([3, "a", ["b"], "c", {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", "c", c: 1)) - assert_equal([3, "a", ["b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", c: 1, "c"=>0)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([3, "a", ["b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.lead_var_trail_hash("a", "b", c: 1, "c"=>0)) + end end def test_opt_var_trail_hash assert_raise(ArgumentError) {Bug::ScanArgs.opt_var_trail_hash()} assert_equal([1, nil, [], "a", nil], Bug::ScanArgs.opt_var_trail_hash("a")) - assert_equal([1, nil, [], {c: 1}, nil], Bug::ScanArgs.opt_var_trail_hash(c: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, nil, [], {c: 1}, nil], Bug::ScanArgs.opt_var_trail_hash(c: 1)) + end assert_equal([1, nil, [], "a", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", c: 1)) assert_equal([2, "a", [], "b", nil], Bug::ScanArgs.opt_var_trail_hash("a", "b")) assert_equal([2, "a", [], "b", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", c: 1)) assert_equal([3, "a", ["b"], "c", nil], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c")) assert_equal([3, "a", ["b"], "c", {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c", c: 1)) - assert_equal([3, "a", ["b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([3, "a", ["b"], {"c"=>0}, {c: 1}], Bug::ScanArgs.opt_var_trail_hash("a", "b", "c"=>0, c: 1)) + end end def test_lead_opt_var_trail_hash assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail_hash()} assert_raise(ArgumentError) {Bug::ScanArgs.lead_opt_var_trail_hash("a")} - assert_equal([2, "a", nil, [], {b: 1}, nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", b: 1)) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([2, "a", nil, [], {b: 1}, nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", b: 1)) + end assert_equal([2, "a", nil, [], "b", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b")) assert_equal([2, "a", nil, [], "b", {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", c: 1)) assert_equal([3, "a", "b", [], "c", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c")) assert_equal([3, "a", "b", [], "c", {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", c: 1)) assert_equal([4, "a", "b", ["c"], "d", nil], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", "d")) - assert_equal([4, "a", "b", ["c"], {"d"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", "d"=>0, c: 1)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([4, "a", "b", ["c"], {"d"=>0}, {c: 1}], Bug::ScanArgs.lead_opt_var_trail_hash("a", "b", "c", "d"=>0, c: 1)) + end + end + + def test_k_lead_opt_hash + assert_raise(ArgumentError) {Bug::ScanArgs.k_lead_opt_hash} + assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", c: 1)) + assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", {c: 1})) + assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b", c: 1)) + assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b", {c: 1})) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.k_lead_opt_hash(c: 1)) + end + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.k_lead_opt_hash("a", "b"=>0, c: 1)) + end + end + + def test_e_lead_opt_hash + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([1, {}, nil, nil], Bug::ScanArgs.e_lead_opt_hash) + end + assert_equal([1, "a", nil, nil], Bug::ScanArgs.e_lead_opt_hash("a")) + assert_equal([2, "a", "b", nil], Bug::ScanArgs.e_lead_opt_hash("a", "b")) + assert_equal([2, "a", {c: 1}, nil], Bug::ScanArgs.e_lead_opt_hash("a", c: 1)) + assert_raise(ArgumentError) {Bug::ScanArgs.e_lead_opt_hash("a", "b", c: 1)} + assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.e_lead_opt_hash(c: 1)) + assert_raise(ArgumentError) {Bug::ScanArgs.e_lead_opt_hash("a", "b", "c")} + assert_equal([2, "a", {"b"=>0, c: 1}, nil], Bug::ScanArgs.e_lead_opt_hash("a", "b"=>0, c: 1)) + end + + def test_n_lead_opt_hash + assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash} + assert_equal([1, "a", nil, nil], Bug::ScanArgs.n_lead_opt_hash("a")) + assert_equal([2, "a", "b", nil], Bug::ScanArgs.n_lead_opt_hash("a", "b")) + assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", c: 1)) + assert_equal([1, "a", nil, {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", {c: 1})) + assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b", c: 1)) + assert_equal([2, "a", "b", {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b", {c: 1})) + assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.n_lead_opt_hash(c: 1)) + assert_equal([1, {c: 1}, nil, nil], Bug::ScanArgs.n_lead_opt_hash({c: 1})) + assert_raise(ArgumentError) {Bug::ScanArgs.n_lead_opt_hash("a", "b", "c")} + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([2, "a", {"b"=>0}, {c: 1}], Bug::ScanArgs.n_lead_opt_hash("a", "b"=>0, c: 1)) + end end end diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index eaecc5247543ff..02d25d15c8c4c1 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -927,7 +927,7 @@ def test_open assert_equal(0444 & ~File.umask, File.stat("b").mode & 0777) assert_equal("def", File.read("b")) - Pathname("c").open("w", 0444, {}) {|f| f.write "ghi" } + Pathname("c").open("w", 0444, **{}) {|f| f.write "ghi" } assert_equal(0444 & ~File.umask, File.stat("c").mode & 0777) assert_equal("ghi", File.read("c")) diff --git a/test/reline/helper.rb b/test/reline/helper.rb index ca0001258d6f80..b1759f1d80ff03 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -31,7 +31,7 @@ class Reline::TestCase < Test::Unit::TestCase if Reline::Unicode::EscapedChars.include?(c.ord) c else - c.encode(@line_editor.instance_variable_get(:@encoding), Encoding::UTF_8, options) + c.encode(@line_editor.instance_variable_get(:@encoding), Encoding::UTF_8, **options) end }.join rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb index 7584074c7ed359..90bf8b399fc7ea 100644 --- a/test/ruby/test_dir_m17n.rb +++ b/test/ruby/test_dir_m17n.rb @@ -18,7 +18,7 @@ def assert_raw_file_name(code, encoding) filename = #{code}.chr('UTF-8').force_encoding("#{encoding}") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename) EOS @@ -26,7 +26,7 @@ def assert_raw_file_name(code, encoding) assert_separately(%w[-EASCII-8BIT], <<-EOS, :chdir=>dir) filename = #{code}.chr('UTF-8').force_encoding("ASCII-8BIT") opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) expected_filename = #{code}.chr('UTF-8').encode(Encoding.find("filesystem")) rescue expected_filename = "?" expected_filename = expected_filename.force_encoding("ASCII-8BIT") if /mswin|mingw/ =~ RUBY_PLATFORM @@ -52,7 +52,7 @@ def test_filename_extutf8 filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename) EOS } @@ -67,7 +67,7 @@ def test_filename_extutf8_invalid filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") assert_include(ents, filename) EOS @@ -75,7 +75,7 @@ def test_filename_extutf8_invalid filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") assert_include(ents, filename) EOS @@ -88,7 +88,7 @@ def test_filename_as_bytes_extutf8 filename = "\xc2\xa1".force_encoding("utf-8") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) @@ -125,13 +125,13 @@ def test_filename_extutf8_inteucjp_representable filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) @@ -151,7 +151,7 @@ def test_filename_extutf8_inteucjp_unrepresentable File.open(filename1, "w") {} File.open(filename2, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename1) assert_include(ents, filename2) EOS @@ -159,7 +159,7 @@ def test_filename_extutf8_inteucjp_unrepresentable filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP filename2 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) assert_include(ents, filename1) assert_include(ents, filename2) EOS @@ -183,7 +183,7 @@ def test_filename_bytes_euc_jp filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) ents.each {|e| e.force_encoding("ASCII-8BIT") } if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8") @@ -200,7 +200,7 @@ def test_filename_euc_jp filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8").force_encoding("euc-jp") end @@ -210,7 +210,7 @@ def test_filename_euc_jp filename = "\xA4\xA2".force_encoding('ASCII-8BIT') win_expected_filename = filename.encode(Encoding.find("filesystem"), "euc-jp") rescue "?" opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) unless ents.include?(filename) case RUBY_PLATFORM when /darwin/ @@ -246,7 +246,7 @@ def test_filename_ext_euc_jp_and_int_utf_8 filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8", "euc-jp").force_encoding("euc-jp") end @@ -255,7 +255,7 @@ def test_filename_ext_euc_jp_and_int_utf_8 assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d) filename = "\u3042" opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", opts||{}) if /darwin/ =~ RUBY_PLATFORM filename = filename.force_encoding("euc-jp") end @@ -420,7 +420,7 @@ def test_entries_compose else orig.each {|o| o.force_encoding(enc) } end - ents = Dir.entries(".", opts).reject {|n| /\A\./ =~ n} + ents = Dir.entries(".", opts||{}).reject {|n| /\A\./ =~ n} ents.sort! PP.assert_equal(orig, ents, bug7267) } diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index 6f098db4545e10..115ff73ea8747e 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -3,7 +3,12 @@ class TestEncodingConverter < Test::Unit::TestCase def check_ec(edst, esrc, eres, dst, src, ec, off, len, opts=nil) - res = ec.primitive_convert(src, dst, off, len, opts) + case opts + when Hash + res = ec.primitive_convert(src, dst, off, len, **opts) + else + res = ec.primitive_convert(src, dst, off, len, opts) + end assert_equal([edst.b, esrc.b, eres], [dst.b, src.b, res]) end diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 9de1b2d82c9b96..e5c38091ac5d73 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -1223,6 +1223,14 @@ def (obj = Object.new).w(n) warn("test warning", uplevel: n) end assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) assert_raise(ArgumentError) {warn("test warning", uplevel: -1)} assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/) + warning = capture_warning_warn {warn("test warning", {uplevel: 0})} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: The last argument is used as the keyword parameter\n", warning[0]) + assert_match(/warning: for method defined here|warning: test warning/, warning[1]) + warning = capture_warning_warn {warn("test warning", **{uplevel: 0})} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + warning = capture_warning_warn {warn("test warning", {uplevel: 0}, **{})} + assert_equal("test warning\n{:uplevel=>0}\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", foo: 1)} end def test_warning_warn_invalid_argument diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index ed60452b36b5b6..a610b608aa1c0f 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -423,7 +423,7 @@ def test_rubydev33072 path = t.path t.close! assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do - File.read(path, nil, nil, {}) + File.read(path, nil, nil, **{}) end end @@ -2489,15 +2489,15 @@ def test_foreach assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:mode => "r" }) {|x| a << x } + IO.foreach(t.path, :mode => "r") {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:open_args => [] }) {|x| a << x } + IO.foreach(t.path, :open_args => []) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:open_args => ["r"] }) {|x| a << x } + IO.foreach(t.path, :open_args => ["r"]) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] @@ -3119,7 +3119,9 @@ def test_s_write assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8")) assert_equal("ff", File.read(path)) assert_raise(TypeError) { - File.write(path, "foo", Object.new => Object.new) + assert_warn(/The last argument is split into positional and keyword parameters/) do + File.write(path, "foo", Object.new => Object.new) + end } end end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 8101bfb62f3a48..022ff330b53f94 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -23,7 +23,8 @@ def with_tmpdir def pipe(*args, wp, rp) re, we = nil, nil - r, w = IO.pipe(*args) + kw = args.last.is_a?(Hash) ? args.pop : {} + r, w = IO.pipe(*args, **kw) rt = Thread.new do begin rp.call(r) diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index 3f87f860b1be7b..7f4a329c4a4aa2 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -189,7 +189,7 @@ def test_frozen_string_in_array_literal def test_debug_frozen_string src = 'n = 1; _="foo#{n ? "-#{n}" : ""}"'; f = "test.rb"; n = 1 opt = {frozen_string_literal: true, debug_frozen_string_literal: true} - str = RubyVM::InstructionSequence.compile(src, f, f, n, opt).eval + str = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval assert_equal("foo-1", str) assert_predicate(str, :frozen?) assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { @@ -200,7 +200,7 @@ def test_debug_frozen_string def test_debug_frozen_string_in_array_literal src = '["foo"]'; f = "test.rb"; n = 1 opt = {frozen_string_literal: true, debug_frozen_string_literal: true} - ary = RubyVM::InstructionSequence.compile(src, f, f, n, opt).eval + ary = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval assert_equal("foo", ary.first) assert_predicate(ary.first, :frozen?) assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 6073ec1ee58068..f87abbb4a8d89e 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -230,7 +230,8 @@ def to_f; -1.5; end end def assert_step(expected, (from, *args), inf: false) - enum = from.step(*args) + kw = args.last.is_a?(Hash) ? args.pop : {} + enum = from.step(*args, **kw) size = enum.size xsize = expected.size @@ -239,7 +240,7 @@ def assert_step(expected, (from, *args), inf: false) assert_send [size, :>, 0], "step size: +infinity" a = [] - from.step(*args) { |x| a << x; break if a.size == xsize } + from.step(*args, **kw) { |x| a << x; break if a.size == xsize } assert_equal expected, a, "step" a = [] @@ -249,7 +250,7 @@ def assert_step(expected, (from, *args), inf: false) assert_equal expected.size, size, "step size" a = [] - from.step(*args) { |x| a << x } + from.step(*args, **kw) { |x| a << x } assert_equal expected, a, "step" a = [] diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 36d246f9e05fc9..dd403bf9d9f62b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -17,8 +17,8 @@ def initialize(*args) super end - def S(*args) - @cls.new(*args) + def S(*args, **kw) + @cls.new(*args, **kw) end def test_s_new