summaryrefslogtreecommitdiff
path: root/vm_args.c
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-04-08 18:45:50 -0700
committerJeremy Evans <code@jeremyevans.net>2019-08-30 12:39:31 -0700
commit896e42d93f52966644c2700ed9fd18f2a1988dd3 (patch)
treed3e4d052f3947dd92db466a43efbde45764dd6e4 /vm_args.c
parent15bca0d4d373820fa648c351dc13c2131849d9b3 (diff)
Restore splitting of hashes into positional and keyword arguments, add warning
This restores compatibility with Ruby 2.6, splitting the last positional hash into positional and keyword arguments if it contains both symbol and non-symbol keys. However, in this case it will warn, as the behavior in Ruby 3 will be to not split the hash and keep it as a positional argument. This does not affect the handling of mixed symbol and non-symbol keys in bare keywords. Those are still treated as keywords now, as they were before this patch. This results in different behavior than Ruby 2.6, which would split the bare keywords and use the non-Symbol keys as a positional arguments.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/2395
Diffstat (limited to 'vm_args.c')
-rw-r--r--vm_args.c74
1 files changed, 62 insertions, 12 deletions
diff --git a/vm_args.c b/vm_args.c
index 9d5626ce97..4e404870da 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -183,23 +183,47 @@ args_rest_array(struct args_info *args)
return ary;
}
+#define KW_HASH_HAS_NO_KEYS 0
+#define KW_HASH_HAS_SYMBOL_KEY 1
+#define KW_HASH_HAS_OTHER_KEY 2
+#define KW_HASH_HAS_BOTH_KEYS 3
+
static int
-keyword_hash_symbol_p(st_data_t key, st_data_t val, st_data_t arg)
+keyword_hash_symbol_other_iter(st_data_t key, st_data_t val, st_data_t arg)
{
- if (SYMBOL_P((VALUE)key)) {
- return ST_CONTINUE;
+ *(int*)arg |= SYMBOL_P((VALUE)key) ? KW_HASH_HAS_SYMBOL_KEY : KW_HASH_HAS_OTHER_KEY;
+
+ if ((*(int*)arg & KW_HASH_HAS_BOTH_KEYS) == KW_HASH_HAS_BOTH_KEYS) {
+ return ST_STOP;
}
- *(VALUE*)arg = (VALUE)0;
- return ST_STOP;
+ return ST_CONTINUE;
+}
+
+static int
+keyword_hash_symbol_other(VALUE hash)
+{
+ int symbol_other = KW_HASH_HAS_NO_KEYS;
+ rb_hash_stlike_foreach(hash, keyword_hash_symbol_other_iter, (st_data_t)(&symbol_other));
+ return symbol_other;
}
static int
-keyword_hash_only_symbol_p(VALUE hash)
+keyword_hash_split_iter(st_data_t key, st_data_t val, st_data_t arg)
+{
+ if (SYMBOL_P((VALUE)key)) {
+ rb_hash_aset((VALUE)arg, (VALUE)key, (VALUE)val);
+ return ST_DELETE;
+ }
+
+ return ST_CONTINUE;
+}
+
+void
+keyword_hash_split(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr)
{
- VALUE all_symbols = (VALUE)(1);
- rb_hash_stlike_foreach(hash, keyword_hash_symbol_p, (st_data_t)(&all_symbols));
- return (int)all_symbols;
+ *kw_hash_ptr = rb_hash_new();
+ rb_hash_stlike_foreach(*rest_hash_ptr, keyword_hash_split_iter, (st_data_t)(*kw_hash_ptr));
}
static int
@@ -208,9 +232,18 @@ 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;
+ if (check_only_symbol) {
+ switch(keyword_hash_symbol_other(*rest_hash_ptr)) {
+ case KW_HASH_HAS_NO_KEYS:
+ case KW_HASH_HAS_SYMBOL_KEY:
+ break;
+ case KW_HASH_HAS_OTHER_KEY:
+ *kw_hash_ptr = Qnil;
+ return FALSE;
+ case KW_HASH_HAS_BOTH_KEYS:
+ keyword_hash_split(kw_hash_ptr, rest_hash_ptr);
+ return TRUE;
+ }
}
*kw_hash_ptr = *rest_hash_ptr;
*rest_hash_ptr = Qfalse;
@@ -552,6 +585,20 @@ get_loc(struct rb_calling_info *calling, const struct rb_call_info *ci)
}
static inline void
+rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci)
+{
+ if (calling->recv == Qundef) return;
+ VALUE loc = get_loc(calling, ci);
+ if (NIL_P(loc)) {
+ rb_warn("The last argument for `%s' is split into positional and keyword parameters", rb_id2name(ci->mid));
+ }
+ else {
+ rb_warn("The last argument for `%s' (defined at %s:%d) is split into positional and keyword parameters",
+ rb_id2name(ci->mid), RSTRING_PTR(RARRAY_AREF(loc, 0)), FIX2INT(RARRAY_AREF(loc, 1)));
+ }
+}
+
+static inline void
rb_warn_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci)
{
if (calling->recv == Qundef) return;
@@ -710,6 +757,9 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
}
given_argc--;
}
+ else if (keyword_hash != Qnil && ec->cfp->iseq) {
+ rb_warn_split_last_hash_to_keyword(calling, ci);
+ }
}
if (given_argc > max_argc && max_argc != UNLIMITED_ARGUMENTS) {