aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-04-07 18:45:26 -0700
committerJeremy Evans <code@jeremyevans.net>2019-08-30 12:39:31 -0700
commit9c2e165f7dbb63dd1921eacae58c4305d3e9e5b6 (patch)
tree16ea05751be006d7cb947fe3057effaad629a793
parente0b4599bba6bea744e0e90b7640dcc21d695c434 (diff)
downloadruby-9c2e165f7dbb63dd1921eacae58c4305d3e9e5b6.tar.gz
Only promote last hash to keyword if all keys are symbols
If all keys are not symbols, then the non-symbol keys would not be treated as keywords in previous versions. It doesn't make sense to treat these hashes as keywords to break compatibility and warn about behavior changes in Ruby 2.7 when the Ruby 3.0 behavior will be the same as the Ruby 2.6 for these hashes.
-rw-r--r--vm_args.c35
1 files changed, 29 insertions, 6 deletions
diff --git a/vm_args.c b/vm_args.c
index 35c435181a..9d5626ce97 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -184,11 +184,34 @@ args_rest_array(struct args_info *args)
}
static int
-keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr)
+keyword_hash_symbol_p(st_data_t key, st_data_t val, st_data_t arg)
+{
+ if (SYMBOL_P((VALUE)key)) {
+ return ST_CONTINUE;
+ }
+
+ *(VALUE*)arg = (VALUE)0;
+ return ST_STOP;
+}
+
+static int
+keyword_hash_only_symbol_p(VALUE hash)
+{
+ VALUE all_symbols = (VALUE)(1);
+ rb_hash_stlike_foreach(hash, keyword_hash_symbol_p, (st_data_t)(&all_symbols));
+ return (int)all_symbols;
+}
+
+static int
+keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr, int check_only_symbol)
{
*rest_hash_ptr = rb_check_hash_type(*kw_hash_ptr);
if (!NIL_P(*rest_hash_ptr)) {
+ if (check_only_symbol && !keyword_hash_only_symbol_p(*rest_hash_ptr)) {
+ *kw_hash_ptr = Qnil;
+ return FALSE;
+ }
*kw_hash_ptr = *rest_hash_ptr;
*rest_hash_ptr = Qfalse;
return TRUE;
@@ -200,7 +223,7 @@ keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr)
}
static VALUE
-args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr)
+args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr, int check_only_symbol)
{
VALUE rest_hash;
@@ -209,7 +232,7 @@ args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr)
VM_ASSERT(args->argc > 0);
*kw_hash_ptr = args->argv[args->argc-1];
- if (keyword_hash_p(kw_hash_ptr, &rest_hash)) {
+ if (keyword_hash_p(kw_hash_ptr, &rest_hash, check_only_symbol)) {
if (rest_hash) {
args->argv[args->argc-1] = rest_hash;
}
@@ -225,7 +248,7 @@ args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr)
if (len > 0) {
*kw_hash_ptr = RARRAY_AREF(args->rest, len - 1);
- if (keyword_hash_p(kw_hash_ptr, &rest_hash)) {
+ if (keyword_hash_p(kw_hash_ptr, &rest_hash, check_only_symbol)) {
if (rest_hash) {
RARRAY_ASET(args->rest, len - 1, rest_hash);
}
@@ -672,11 +695,11 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
(kw_splat && given_argc > max_argc)) &&
args->kw_argv == NULL) {
if (((kw_flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT)) || !ec->cfp->iseq /* called from C */)) {
- if (args_pop_keyword_hash(args, &keyword_hash)) {
+ if (args_pop_keyword_hash(args, &keyword_hash, 0)) {
given_argc--;
}
}
- else if (args_pop_keyword_hash(args, &keyword_hash)) {
+ else if (args_pop_keyword_hash(args, &keyword_hash, 1)) {
/* Warn the following:
* def foo(k:1) p [k]; end
* foo({k:42}) #=> 42