summaryrefslogtreecommitdiff
path: root/hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'hash.c')
-rw-r--r--hash.c3081
1 files changed, 1620 insertions, 1461 deletions
diff --git a/hash.c b/hash.c
index 2b243d9e66..1dabdfb4a2 100644
--- a/hash.c
+++ b/hash.c
@@ -35,6 +35,7 @@
#include "internal/hash.h"
#include "internal/object.h"
#include "internal/proc.h"
+#include "internal/st.h"
#include "internal/symbol.h"
#include "internal/thread.h"
#include "internal/time.h"
@@ -44,19 +45,32 @@
#include "ruby/util.h"
#include "ruby_assert.h"
#include "symbol.h"
-#include "transient_heap.h"
#include "ruby/thread_native.h"
#include "ruby/ractor.h"
#include "vm_sync.h"
+#include "builtin.h"
+
+/* Flags of RHash
+ *
+ * 1: RHASH_PASS_AS_KEYWORDS
+ * The hash is flagged as Ruby 2 keywords hash.
+ * 2: RHASH_PROC_DEFAULT
+ * The hash has a default proc (rather than a default value).
+ * 3: RHASH_ST_TABLE_FLAG
+ * The hash uses a ST table (rather than an AR table).
+ * 4-7: RHASH_AR_TABLE_SIZE_MASK
+ * The size of the AR table.
+ * 8-11: RHASH_AR_TABLE_BOUND_MASK
+ * The bounds of the AR table.
+ * 13-19: RHASH_LEV_MASK
+ * The iterational level of the hash. Used to prevent modifications
+ * to the hash during iteration.
+ */
#ifndef HASH_DEBUG
#define HASH_DEBUG 0
#endif
-#if HASH_DEBUG
-#include "gc.h"
-#endif
-
#define SET_DEFAULT(hash, ifnone) ( \
FL_UNSET_RAW(hash, RHASH_PROC_DEFAULT), \
RHASH_SET_IFNONE(hash, ifnone))
@@ -85,6 +99,7 @@ static VALUE rb_hash_s_try_convert(VALUE, VALUE);
* 2. Insert WBs
*/
+/* :nodoc: */
VALUE
rb_hash_freeze(VALUE hash)
{
@@ -92,6 +107,7 @@ rb_hash_freeze(VALUE hash)
}
VALUE rb_cHash;
+VALUE rb_cHash_empty_frozen;
static VALUE envtbl;
static ID id_hash, id_flatten_bang;
@@ -106,7 +122,7 @@ rb_hash_set_ifnone(VALUE hash, VALUE ifnone)
return hash;
}
-static int
+int
rb_any_cmp(VALUE a, VALUE b)
{
if (a == b) return 0;
@@ -164,7 +180,7 @@ any_hash(VALUE a, st_index_t (*other_func)(VALUE))
hnum = rb_hash_start(hnum);
}
else {
- hnum = RSYMBOL(a)->hashval;
+ hnum = RSHIFT(RSYMBOL(a)->hashval, 1);
}
break;
case T_FIXNUM:
@@ -193,10 +209,26 @@ any_hash(VALUE a, st_index_t (*other_func)(VALUE))
return (long)hnum;
}
+VALUE rb_obj_hash(VALUE obj);
+VALUE rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat);
+
static st_index_t
obj_any_hash(VALUE obj)
{
- VALUE hval = rb_check_funcall_basic_kw(obj, id_hash, rb_mKernel, 0, 0, 0);
+ VALUE hval = Qundef;
+ VALUE klass = CLASS_OF(obj);
+ if (klass) {
+ const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, id_hash);
+ if (cme && METHOD_ENTRY_BASIC(cme)) {
+ // Optimize away the frame push overhead if it's the default Kernel#hash
+ if (cme->def->type == VM_METHOD_TYPE_CFUNC && cme->def->body.cfunc.func == (rb_cfunc_t)rb_obj_hash) {
+ hval = rb_obj_hash(obj);
+ }
+ else if (RBASIC_CLASS(cme->defined_class) == rb_mKernel) {
+ hval = rb_vm_call0(GET_EC(), obj, id_hash, 0, 0, cme, 0);
+ }
+ }
+ }
if (UNDEF_P(hval)) {
hval = rb_exec_recursive_outer_mid(hash_recursive, obj, 0, id_hash);
@@ -221,7 +253,7 @@ obj_any_hash(VALUE obj)
return FIX2LONG(hval);
}
-static st_index_t
+st_index_t
rb_any_hash(VALUE a)
{
return any_hash(a, obj_any_hash);
@@ -289,40 +321,35 @@ objid_hash(VALUE obj)
#endif
}
-/**
+/*
* call-seq:
- * obj.hash -> integer
- *
- * Generates an Integer hash value for this object. This function must have the
- * property that <code>a.eql?(b)</code> implies <code>a.hash == b.hash</code>.
- *
- * The hash value is used along with #eql? by the Hash class to determine if
- * two objects reference the same hash key. Any hash value that exceeds the
- * capacity of an Integer will be truncated before being used.
+ * hash -> integer
*
- * The hash value for an object may not be identical across invocations or
- * implementations of Ruby. If you need a stable identifier across Ruby
- * invocations and implementations you will need to generate one with a custom
- * method.
+ * Returns the integer hash value for +self+;
+ * has the property that if <tt>foo.eql?(bar)</tt>
+ * then <tt>foo.hash == bar.hash</tt>.
*
- * Certain core classes such as Integer use built-in hash calculations and
- * do not call the #hash method when used as a hash key.
+ * \Class Hash uses both #hash and #eql? to determine whether two objects
+ * used as hash keys are to be treated as the same key.
+ * A hash value that exceeds the capacity of an Integer is truncated before being used.
*
- * When implementing your own #hash based on multiple values, the best
- * practice is to combine the class and any values using the hash code of an
- * array:
+ * Many core classes override method Object#hash;
+ * other core classes (e.g., Integer) calculate the hash internally,
+ * and do not call the #hash method when used as a hash key.
*
- * For example:
+ * When implementing #hash for a user-defined class,
+ * best practice is to use Array#hash with the class name and the values
+ * that are important in the instance;
+ * this takes advantage of that method's logic for safely and efficiently
+ * generating a hash value:
*
* def hash
* [self.class, a, b, c].hash
* end
*
- * The reason for this is that the Array#hash method already has logic for
- * safely and efficiently combining multiple hash values.
- *--
- * \private
- *++
+ * The hash value may differ among invocations or implementations of Ruby.
+ * If you need stable hash-like identifiers across Ruby invocations and implementations,
+ * use a custom method to generate them.
*/
VALUE
rb_obj_hash(VALUE obj)
@@ -361,16 +388,17 @@ const struct st_hash_type rb_hashtype_ident = {
rb_ident_hash,
};
+#define RHASH_IDENTHASH_P(hash) (RHASH_TYPE(hash) == &identhash)
+#define RHASH_STRING_KEY_P(hash, key) (!RHASH_IDENTHASH_P(hash) && (rb_obj_class(key) == rb_cString))
+
typedef st_index_t st_hash_t;
/*
* RHASH_AR_TABLE_P(h):
- * * as.ar == NULL or
- * as.ar points ar_table.
- * * as.ar is allocated by transient heap or xmalloc.
+ * RHASH_AR_TABLE points to ar_table.
*
* !RHASH_AR_TABLE_P(h):
- * * as.st points st_table.
+ * RHASH_ST_TABLE points st_table.
*/
#define RHASH_AR_TABLE_MAX_BOUND RHASH_AR_TABLE_MAX_SIZE
@@ -378,22 +406,6 @@ typedef st_index_t st_hash_t;
#define RHASH_AR_TABLE_REF(hash, n) (&RHASH_AR_TABLE(hash)->pairs[n])
#define RHASH_AR_CLEARED_HINT 0xff
-typedef struct ar_table_pair_struct {
- VALUE key;
- VALUE val;
-} ar_table_pair;
-
-typedef struct ar_table_struct {
- /* 64bit CPU: 8B * 2 * 8 = 128B */
- ar_table_pair pairs[RHASH_AR_TABLE_MAX_SIZE];
-} ar_table;
-
-size_t
-rb_hash_ar_table_size(void)
-{
- return sizeof(ar_table);
-}
-
static inline st_hash_t
ar_do_hash(st_data_t key)
{
@@ -409,13 +421,13 @@ ar_do_hash_hint(st_hash_t hash_value)
static inline ar_hint_t
ar_hint(VALUE hash, unsigned int index)
{
- return RHASH(hash)->ar_hint.ary[index];
+ return RHASH_AR_TABLE(hash)->ar_hint.ary[index];
}
static inline void
ar_hint_set_hint(VALUE hash, unsigned int index, ar_hint_t hint)
{
- RHASH(hash)->ar_hint.ary[index] = hint;
+ RHASH_AR_TABLE(hash)->ar_hint.ary[index] = hint;
}
static inline void
@@ -463,49 +475,23 @@ ar_set_entry(VALUE hash, unsigned int index, st_data_t key, st_data_t val, st_ha
((unsigned int)((RBASIC(h)->flags >> RHASH_AR_TABLE_BOUND_SHIFT) & \
(RHASH_AR_TABLE_BOUND_MASK >> RHASH_AR_TABLE_BOUND_SHIFT)))
-#define RHASH_AR_TABLE_BOUND(h) (HASH_ASSERT(RHASH_AR_TABLE_P(h)), \
- RHASH_AR_TABLE_BOUND_RAW(h))
-
#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s)
#define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type)
#define HASH_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(HASH_DEBUG, expr, #expr)
-#if HASH_DEBUG
-#define hash_verify(hash) hash_verify_(hash, __FILE__, __LINE__)
-
-void
-rb_hash_dump(VALUE hash)
+static inline unsigned int
+RHASH_AR_TABLE_BOUND(VALUE h)
{
- rb_obj_info_dump(hash);
-
- if (RHASH_AR_TABLE_P(hash)) {
- unsigned i, n = 0, bound = RHASH_AR_TABLE_BOUND(hash);
-
- fprintf(stderr, " size:%u bound:%u\n",
- RHASH_AR_TABLE_SIZE(hash), RHASH_AR_TABLE_BOUND(hash));
-
- for (i=0; i<bound; i++) {
- st_data_t k, v;
-
- if (!ar_cleared_entry(hash, i)) {
- char b1[0x100], b2[0x100];
- ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
- k = pair->key;
- v = pair->val;
- fprintf(stderr, " %d key:%s val:%s hint:%02x\n", i,
- rb_raw_obj_info(b1, 0x100, k),
- rb_raw_obj_info(b2, 0x100, v),
- ar_hint(hash, i));
- n++;
- }
- else {
- fprintf(stderr, " %d empty\n", i);
- }
- }
- }
+ HASH_ASSERT(RHASH_AR_TABLE_P(h));
+ const unsigned int bound = RHASH_AR_TABLE_BOUND_RAW(h);
+ HASH_ASSERT(bound <= RHASH_AR_TABLE_MAX_SIZE);
+ return bound;
}
+#if HASH_DEBUG
+#define hash_verify(hash) hash_verify_(hash, __FILE__, __LINE__)
+
static VALUE
hash_verify_(VALUE hash, const char *file, int line)
{
@@ -535,13 +521,6 @@ hash_verify_(VALUE hash, const char *file, int line)
HASH_ASSERT(RHASH_AR_TABLE_BOUND_RAW(hash) == 0);
}
-#if USE_TRANSIENT_HEAP
- if (RHASH_TRANSIENT_P(hash)) {
- volatile st_data_t MAYBE_UNUSED(key) = RHASH_AR_TABLE_REF(hash, 0)->key; /* read */
- HASH_ASSERT(RHASH_AR_TABLE(hash) != NULL);
- HASH_ASSERT(rb_transient_heap_managed_ptr_p(RHASH_AR_TABLE(hash)));
- }
-#endif
return hash;
}
@@ -550,69 +529,30 @@ hash_verify_(VALUE hash, const char *file, int line)
#endif
static inline int
-RHASH_TABLE_NULL_P(VALUE hash)
-{
- if (RHASH(hash)->as.ar == NULL) {
- HASH_ASSERT(RHASH_AR_TABLE_P(hash));
- return TRUE;
- }
- else {
- return FALSE;
- }
-}
-
-static inline int
RHASH_TABLE_EMPTY_P(VALUE hash)
{
return RHASH_SIZE(hash) == 0;
}
-int
-rb_hash_ar_table_p(VALUE hash)
-{
- if (FL_TEST_RAW((hash), RHASH_ST_TABLE_FLAG)) {
- HASH_ASSERT(RHASH(hash)->as.st != NULL);
- return FALSE;
- }
- else {
- return TRUE;
- }
-}
-
-ar_table *
-rb_hash_ar_table(VALUE hash)
-{
- HASH_ASSERT(RHASH_AR_TABLE_P(hash));
- return RHASH(hash)->as.ar;
-}
+#define RHASH_SET_ST_FLAG(h) FL_SET_RAW(h, RHASH_ST_TABLE_FLAG)
+#define RHASH_UNSET_ST_FLAG(h) FL_UNSET_RAW(h, RHASH_ST_TABLE_FLAG)
-st_table *
-rb_hash_st_table(VALUE hash)
+static void
+hash_st_table_init(VALUE hash, const struct st_hash_type *type, st_index_t size)
{
- HASH_ASSERT(!RHASH_AR_TABLE_P(hash));
- return RHASH(hash)->as.st;
+ st_init_existing_table_with_size(RHASH_ST_TABLE(hash), type, size);
+ RHASH_SET_ST_FLAG(hash);
}
void
rb_hash_st_table_set(VALUE hash, st_table *st)
{
HASH_ASSERT(st != NULL);
- FL_SET_RAW((hash), RHASH_ST_TABLE_FLAG);
- RHASH(hash)->as.st = st;
-}
+ RHASH_SET_ST_FLAG(hash);
-static void
-hash_ar_table_set(VALUE hash, ar_table *ar)
-{
- HASH_ASSERT(RHASH_AR_TABLE_P(hash));
- HASH_ASSERT((RHASH_TRANSIENT_P(hash) && ar == NULL) ? FALSE : TRUE);
- RHASH(hash)->as.ar = ar;
- hash_verify(hash);
+ *RHASH_ST_TABLE(hash) = *st;
}
-#define RHASH_SET_ST_FLAG(h) FL_SET_RAW(h, RHASH_ST_TABLE_FLAG)
-#define RHASH_UNSET_ST_FLAG(h) FL_UNSET_RAW(h, RHASH_ST_TABLE_FLAG)
-
static inline void
RHASH_AR_TABLE_BOUND_SET(VALUE h, st_index_t n)
{
@@ -667,27 +607,7 @@ RHASH_AR_TABLE_CLEAR(VALUE h)
RBASIC(h)->flags &= ~RHASH_AR_TABLE_SIZE_MASK;
RBASIC(h)->flags &= ~RHASH_AR_TABLE_BOUND_MASK;
- hash_ar_table_set(h, NULL);
-}
-
-static ar_table*
-ar_alloc_table(VALUE hash)
-{
- ar_table *tab = (ar_table*)rb_transient_heap_alloc(hash, sizeof(ar_table));
-
- if (tab != NULL) {
- RHASH_SET_TRANSIENT_FLAG(hash);
- }
- else {
- RHASH_UNSET_TRANSIENT_FLAG(hash);
- tab = (ar_table*)ruby_xmalloc(sizeof(ar_table));
- }
-
- RHASH_AR_TABLE_SIZE_SET(hash, 0);
- RHASH_AR_TABLE_BOUND_SET(hash, 0);
- hash_ar_table_set(hash, tab);
-
- return tab;
+ memset(RHASH_AR_TABLE(h), 0, sizeof(ar_table));
}
NOINLINE(static int ar_equal(VALUE x, VALUE y));
@@ -702,7 +622,7 @@ static unsigned
ar_find_entry_hint(VALUE hash, ar_hint_t hint, st_data_t key)
{
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
- const ar_hint_t *hints = RHASH(hash)->ar_hint.ary;
+ const ar_hint_t *hints = RHASH_AR_TABLE(hash)->ar_hint.ary;
/* if table is NULL, then bound also should be 0 */
@@ -749,92 +669,83 @@ ar_find_entry(VALUE hash, st_hash_t hash_value, st_data_t key)
}
static inline void
-ar_free_and_clear_table(VALUE hash)
+hash_ar_free_and_clear_table(VALUE hash)
{
- ar_table *tab = RHASH_AR_TABLE(hash);
+ RHASH_AR_TABLE_CLEAR(hash);
- if (tab) {
- if (RHASH_TRANSIENT_P(hash)) {
- RHASH_UNSET_TRANSIENT_FLAG(hash);
- }
- else {
- ruby_xfree(RHASH_AR_TABLE(hash));
- }
- RHASH_AR_TABLE_CLEAR(hash);
- }
HASH_ASSERT(RHASH_AR_TABLE_SIZE(hash) == 0);
HASH_ASSERT(RHASH_AR_TABLE_BOUND(hash) == 0);
- HASH_ASSERT(RHASH_TRANSIENT_P(hash) == 0);
}
-static void
-ar_try_convert_table(VALUE hash)
-{
- if (!RHASH_AR_TABLE_P(hash)) return;
-
- const unsigned size = RHASH_AR_TABLE_SIZE(hash);
+void rb_st_add_direct_with_hash(st_table *tab, st_data_t key, st_data_t value, st_hash_t hash); // st.c
- st_table *new_tab;
- st_index_t i;
+enum ar_each_key_type {
+ ar_each_key_copy,
+ ar_each_key_cmp,
+ ar_each_key_insert,
+};
- if (size < RHASH_AR_TABLE_MAX_SIZE) {
- return;
+static inline int
+ar_each_key(ar_table *ar, int max, enum ar_each_key_type type, st_data_t *dst_keys, st_table *new_tab, st_hash_t *hashes)
+{
+ for (int i = 0; i < max; i++) {
+ ar_table_pair *pair = &ar->pairs[i];
+
+ switch (type) {
+ case ar_each_key_copy:
+ dst_keys[i] = pair->key;
+ break;
+ case ar_each_key_cmp:
+ if (dst_keys[i] != pair->key) return 1;
+ break;
+ case ar_each_key_insert:
+ if (UNDEF_P(pair->key)) continue; // deleted entry
+ rb_st_add_direct_with_hash(new_tab, pair->key, pair->val, hashes[i]);
+ break;
+ }
}
- new_tab = st_init_table_with_size(&objhash, size * 2);
-
- for (i = 0; i < RHASH_AR_TABLE_MAX_BOUND; i++) {
- ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
- st_add_direct(new_tab, pair->key, pair->val);
- }
- ar_free_and_clear_table(hash);
- RHASH_ST_TABLE_SET(hash, new_tab);
- return;
+ return 0;
}
static st_table *
ar_force_convert_table(VALUE hash, const char *file, int line)
{
- st_table *new_tab;
-
if (RHASH_ST_TABLE_P(hash)) {
return RHASH_ST_TABLE(hash);
}
-
- if (RHASH_AR_TABLE(hash)) {
- unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
-
-#if defined(RHASH_CONVERT_TABLE_DEBUG) && RHASH_CONVERT_TABLE_DEBUG
- rb_obj_info_dump(hash);
- fprintf(stderr, "force_convert: %s:%d\n", file, line);
- RB_DEBUG_COUNTER_INC(obj_hash_force_convert);
-#endif
-
- new_tab = st_init_table_with_size(&objhash, RHASH_AR_TABLE_SIZE(hash));
-
- for (i = 0; i < bound; i++) {
- if (ar_cleared_entry(hash, i)) continue;
-
- ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
- st_add_direct(new_tab, pair->key, pair->val);
- }
- ar_free_and_clear_table(hash);
- }
else {
- new_tab = st_init_table(&objhash);
- }
- RHASH_ST_TABLE_SET(hash, new_tab);
-
- return new_tab;
-}
+ ar_table *ar = RHASH_AR_TABLE(hash);
+ st_hash_t hashes[RHASH_AR_TABLE_MAX_SIZE];
+ unsigned int bound, size;
+
+ // prepare hash values
+ do {
+ st_data_t keys[RHASH_AR_TABLE_MAX_SIZE];
+ bound = RHASH_AR_TABLE_BOUND(hash);
+ size = RHASH_AR_TABLE_SIZE(hash);
+ ar_each_key(ar, bound, ar_each_key_copy, keys, NULL, NULL);
+
+ for (unsigned int i = 0; i < bound; i++) {
+ // do_hash calls #hash method and it can modify hash object
+ hashes[i] = UNDEF_P(keys[i]) ? 0 : ar_do_hash(keys[i]);
+ }
-static ar_table *
-hash_ar_table(VALUE hash)
-{
- if (RHASH_TABLE_NULL_P(hash)) {
- ar_alloc_table(hash);
+ // check if modified
+ if (UNLIKELY(!RHASH_AR_TABLE_P(hash))) return RHASH_ST_TABLE(hash);
+ if (UNLIKELY(RHASH_AR_TABLE_BOUND(hash) != bound)) continue;
+ if (UNLIKELY(ar_each_key(ar, bound, ar_each_key_cmp, keys, NULL, NULL))) continue;
+ } while (0);
+
+ // make st
+ st_table tab;
+ st_table *new_tab = &tab;
+ st_init_existing_table_with_size(new_tab, &objhash, size);
+ ar_each_key(ar, bound, ar_each_key_insert, NULL, new_tab, hashes);
+ hash_ar_free_and_clear_table(hash);
+ RHASH_ST_TABLE_SET(hash, new_tab);
+ return RHASH_ST_TABLE(hash);
}
- return RHASH_AR_TABLE(hash);
}
static int
@@ -887,7 +798,6 @@ ar_add_direct_with_hash(VALUE hash, st_data_t key, st_data_t val, st_hash_t hash
else {
if (UNLIKELY(bin >= RHASH_AR_TABLE_MAX_BOUND)) {
bin = ar_compact_table(hash);
- hash_ar_table(hash);
}
HASH_ASSERT(bin < RHASH_AR_TABLE_MAX_BOUND);
@@ -898,6 +808,14 @@ ar_add_direct_with_hash(VALUE hash, st_data_t key, st_data_t val, st_hash_t hash
}
}
+static void
+ensure_ar_table(VALUE hash)
+{
+ if (!RHASH_AR_TABLE_P(hash)) {
+ rb_raise(rb_eRuntimeError, "hash representation was changed during iteration");
+ }
+}
+
static int
ar_general_foreach(VALUE hash, st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg)
{
@@ -908,7 +826,10 @@ ar_general_foreach(VALUE hash, st_foreach_check_callback_func *func, st_update_c
if (ar_cleared_entry(hash, i)) continue;
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
- enum st_retval retval = (*func)(pair->key, pair->val, arg, 0);
+ st_data_t key = (st_data_t)pair->key;
+ st_data_t val = (st_data_t)pair->val;
+ enum st_retval retval = (*func)(key, val, arg, 0);
+ ensure_ar_table(hash);
/* pair may be not valid here because of theap */
switch (retval) {
@@ -919,14 +840,13 @@ ar_general_foreach(VALUE hash, st_foreach_check_callback_func *func, st_update_c
return 0;
case ST_REPLACE:
if (replace) {
- VALUE key = pair->key;
- VALUE val = pair->val;
- retval = (*replace)(&key, &val, arg, TRUE);
-
- // TODO: pair should be same as pair before.
- ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
- pair->key = key;
- pair->val = val;
+ (*replace)(&key, &val, arg, TRUE);
+
+ // Pair should not have moved
+ HASH_ASSERT(pair == RHASH_AR_TABLE_REF(hash, i));
+
+ pair->key = (VALUE)key;
+ pair->val = (VALUE)val;
}
break;
case ST_DELETE:
@@ -983,6 +903,7 @@ ar_foreach_check(VALUE hash, st_foreach_check_callback_func *func, st_data_t arg
hint = ar_hint(hash, i);
retval = (*func)(key, pair->val, arg, 0);
+ ensure_ar_table(hash);
hash_verify(hash);
switch (retval) {
@@ -991,7 +912,7 @@ ar_foreach_check(VALUE hash, st_foreach_check_callback_func *func, st_data_t arg
if (pair->key == never) break;
ret = ar_find_entry_hint(hash, hint, key);
if (ret == RHASH_AR_TABLE_MAX_BOUND) {
- retval = (*func)(0, 0, arg, 1);
+ (*func)(0, 0, arg, 1);
return 2;
}
}
@@ -1032,7 +953,6 @@ ar_update(VALUE hash, st_data_t key,
existing = (bin != RHASH_AR_TABLE_MAX_BOUND) ? TRUE : FALSE;
}
else {
- hash_ar_table(hash); /* allocate ltbl if needed */
existing = FALSE;
}
@@ -1044,6 +964,7 @@ ar_update(VALUE hash, st_data_t key,
old_key = key;
retval = (*func)(&key, &value, arg, existing);
/* pair can be invalid here because of theap */
+ ensure_ar_table(hash);
switch (retval) {
case ST_CONTINUE:
@@ -1081,8 +1002,6 @@ ar_insert(VALUE hash, st_data_t key, st_data_t value)
return -1;
}
- hash_ar_table(hash); /* prepare ltbl */
-
bin = ar_find_entry(hash, hash_value, key);
if (bin == RHASH_AR_TABLE_MAX_BOUND) {
if (RHASH_AR_TABLE_SIZE(hash) >= RHASH_AR_TABLE_MAX_SIZE) {
@@ -1090,7 +1009,6 @@ ar_insert(VALUE hash, st_data_t key, st_data_t value)
}
else if (bin >= RHASH_AR_TABLE_MAX_BOUND) {
bin = ar_compact_table(hash);
- hash_ar_table(hash);
}
HASH_ASSERT(bin < RHASH_AR_TABLE_MAX_BOUND);
@@ -1225,44 +1143,16 @@ static ar_table*
ar_copy(VALUE hash1, VALUE hash2)
{
ar_table *old_tab = RHASH_AR_TABLE(hash2);
+ ar_table *new_tab = RHASH_AR_TABLE(hash1);
- if (old_tab != NULL) {
- ar_table *new_tab = RHASH_AR_TABLE(hash1);
- if (new_tab == NULL) {
- new_tab = (ar_table*) rb_transient_heap_alloc(hash1, sizeof(ar_table));
- if (new_tab != NULL) {
- RHASH_SET_TRANSIENT_FLAG(hash1);
- }
- else {
- RHASH_UNSET_TRANSIENT_FLAG(hash1);
- new_tab = (ar_table*)ruby_xmalloc(sizeof(ar_table));
- }
- }
- *new_tab = *old_tab;
- RHASH(hash1)->ar_hint.word = RHASH(hash2)->ar_hint.word;
- RHASH_AR_TABLE_BOUND_SET(hash1, RHASH_AR_TABLE_BOUND(hash2));
- RHASH_AR_TABLE_SIZE_SET(hash1, RHASH_AR_TABLE_SIZE(hash2));
- hash_ar_table_set(hash1, new_tab);
-
- rb_gc_writebarrier_remember(hash1);
- return new_tab;
- }
- else {
- RHASH_AR_TABLE_BOUND_SET(hash1, RHASH_AR_TABLE_BOUND(hash2));
- RHASH_AR_TABLE_SIZE_SET(hash1, RHASH_AR_TABLE_SIZE(hash2));
-
- if (RHASH_TRANSIENT_P(hash1)) {
- RHASH_UNSET_TRANSIENT_FLAG(hash1);
- }
- else if (RHASH_AR_TABLE(hash1)) {
- ruby_xfree(RHASH_AR_TABLE(hash1));
- }
+ *new_tab = *old_tab;
+ RHASH_AR_TABLE(hash1)->ar_hint.word = RHASH_AR_TABLE(hash2)->ar_hint.word;
+ RHASH_AR_TABLE_BOUND_SET(hash1, RHASH_AR_TABLE_BOUND(hash2));
+ RHASH_AR_TABLE_SIZE_SET(hash1, RHASH_AR_TABLE_SIZE(hash2));
- hash_ar_table_set(hash1, NULL);
+ rb_gc_writebarrier_remember(hash1);
- rb_gc_writebarrier_remember(hash1);
- return old_tab;
- }
+ return new_tab;
}
static void
@@ -1278,32 +1168,32 @@ ar_clear(VALUE hash)
}
}
-#if USE_TRANSIENT_HEAP
-void
-rb_hash_transient_heap_evacuate(VALUE hash, int promote)
+static void
+hash_st_free(VALUE hash)
{
- if (RHASH_TRANSIENT_P(hash)) {
- ar_table *new_tab;
- ar_table *old_tab = RHASH_AR_TABLE(hash);
+ HASH_ASSERT(RHASH_ST_TABLE_P(hash));
- if (UNLIKELY(old_tab == NULL)) {
- return;
- }
- HASH_ASSERT(old_tab != NULL);
- if (! promote) {
- new_tab = rb_transient_heap_alloc(hash, sizeof(ar_table));
- if (new_tab == NULL) promote = true;
- }
- if (promote) {
- new_tab = ruby_xmalloc(sizeof(ar_table));
- RHASH_UNSET_TRANSIENT_FLAG(hash);
- }
- *new_tab = *old_tab;
- hash_ar_table_set(hash, new_tab);
+ st_table *tab = RHASH_ST_TABLE(hash);
+
+ xfree(tab->bins);
+ xfree(tab->entries);
+}
+
+static void
+hash_st_free_and_clear_table(VALUE hash)
+{
+ hash_st_free(hash);
+
+ RHASH_ST_CLEAR(hash);
+}
+
+void
+rb_hash_free(VALUE hash)
+{
+ if (RHASH_ST_TABLE_P(hash)) {
+ hash_st_free(hash);
}
- hash_verify(hash);
}
-#endif
typedef int st_foreach_func(st_data_t, st_data_t, st_data_t);
@@ -1371,7 +1261,6 @@ hash_ar_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error)
if (error) return ST_STOP;
int status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg);
- /* TODO: rehash check? rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); */
return hash_iter_status_check(status);
}
@@ -1383,89 +1272,84 @@ hash_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int error)
if (error) return ST_STOP;
- st_table *tbl = RHASH_ST_TABLE(arg->hash);
int status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg);
- if (RHASH_ST_TABLE(arg->hash) != tbl) {
- rb_raise(rb_eRuntimeError, "rehash occurred during iteration");
- }
-
return hash_iter_status_check(status);
}
-static int
+static unsigned long
iter_lev_in_ivar(VALUE hash)
{
VALUE levval = rb_ivar_get(hash, id_hash_iter_lev);
HASH_ASSERT(FIXNUM_P(levval));
- return FIX2INT(levval);
+ long lev = FIX2LONG(levval);
+ HASH_ASSERT(lev >= 0);
+ return (unsigned long)lev;
}
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
static void
-iter_lev_in_ivar_set(VALUE hash, int lev)
+iter_lev_in_ivar_set(VALUE hash, unsigned long lev)
{
- rb_ivar_set_internal(hash, id_hash_iter_lev, INT2FIX(lev));
+ HASH_ASSERT(lev >= RHASH_LEV_MAX);
+ HASH_ASSERT(POSFIXABLE(lev)); /* POSFIXABLE means fitting to long */
+ rb_ivar_set_internal(hash, id_hash_iter_lev, LONG2FIX((long)lev));
}
-static int
+static inline unsigned long
iter_lev_in_flags(VALUE hash)
{
- unsigned int u = (unsigned int)((RBASIC(hash)->flags >> RHASH_LEV_SHIFT) & RHASH_LEV_MAX);
- return (int)u;
+ return (unsigned long)((RBASIC(hash)->flags >> RHASH_LEV_SHIFT) & RHASH_LEV_MAX);
}
-static int
-RHASH_ITER_LEV(VALUE hash)
+static inline void
+iter_lev_in_flags_set(VALUE hash, unsigned long lev)
{
- int lev = iter_lev_in_flags(hash);
+ HASH_ASSERT(lev <= RHASH_LEV_MAX);
+ RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((VALUE)lev << RHASH_LEV_SHIFT));
+}
- if (lev == RHASH_LEV_MAX) {
- return iter_lev_in_ivar(hash);
- }
- else {
- return lev;
- }
+static inline bool
+hash_iterating_p(VALUE hash)
+{
+ return iter_lev_in_flags(hash) > 0;
}
static void
hash_iter_lev_inc(VALUE hash)
{
- int lev = iter_lev_in_flags(hash);
+ unsigned long lev = iter_lev_in_flags(hash);
if (lev == RHASH_LEV_MAX) {
- lev = iter_lev_in_ivar(hash);
- iter_lev_in_ivar_set(hash, lev+1);
+ lev = iter_lev_in_ivar(hash) + 1;
+ if (!POSFIXABLE(lev)) { /* paranoiac check */
+ rb_raise(rb_eRuntimeError, "too much nested iterations");
+ }
}
else {
lev += 1;
- RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((VALUE)lev << RHASH_LEV_SHIFT));
- if (lev == RHASH_LEV_MAX) {
- iter_lev_in_ivar_set(hash, lev);
- }
+ iter_lev_in_flags_set(hash, lev);
+ if (lev < RHASH_LEV_MAX) return;
}
+ iter_lev_in_ivar_set(hash, lev);
}
static void
hash_iter_lev_dec(VALUE hash)
{
- int lev = iter_lev_in_flags(hash);
+ unsigned long lev = iter_lev_in_flags(hash);
if (lev == RHASH_LEV_MAX) {
lev = iter_lev_in_ivar(hash);
- HASH_ASSERT(lev > 0);
- iter_lev_in_ivar_set(hash, lev-1);
+ if (lev > RHASH_LEV_MAX) {
+ iter_lev_in_ivar_set(hash, lev-1);
+ return;
+ }
+ rb_attr_delete(hash, id_hash_iter_lev);
}
- else {
- HASH_ASSERT(lev > 0);
- RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((lev-1) << RHASH_LEV_SHIFT));
+ else if (lev == 0) {
+ rb_raise(rb_eRuntimeError, "iteration level underflow");
}
-}
-
-static VALUE
-hash_foreach_ensure_rollback(VALUE hash)
-{
- hash_iter_lev_inc(hash);
- return 0;
+ iter_lev_in_flags_set(hash, lev - 1);
}
static VALUE
@@ -1475,6 +1359,7 @@ hash_foreach_ensure(VALUE hash)
return 0;
}
+/* This does not manage iteration level */
int
rb_hash_stlike_foreach(VALUE hash, st_foreach_callback_func *func, st_data_t arg)
{
@@ -1486,6 +1371,7 @@ rb_hash_stlike_foreach(VALUE hash, st_foreach_callback_func *func, st_data_t arg
}
}
+/* This does not manage iteration level */
int
rb_hash_stlike_foreach_with_replace(VALUE hash, st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg)
{
@@ -1536,11 +1422,23 @@ rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg)
hash_verify(hash);
}
+void rb_st_compact_table(st_table *tab);
+
+static void
+compact_after_delete(VALUE hash)
+{
+ if (!hash_iterating_p(hash) && RHASH_ST_TABLE_P(hash)) {
+ rb_st_compact_table(RHASH_ST_TABLE(hash));
+ }
+}
+
static VALUE
-hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone)
+hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone, bool st)
{
const VALUE wb = (RGENGC_WB_PROTECTED_HASH ? FL_WB_PROTECTED : 0);
- NEWOBJ_OF(hash, struct RHash, klass, T_HASH | wb | flags);
+ const size_t size = sizeof(struct RHash) + (st ? sizeof(st_table) : sizeof(ar_table));
+
+ NEWOBJ_OF(hash, struct RHash, klass, T_HASH | wb | flags, size, 0);
RHASH_SET_IFNONE((VALUE)hash, ifnone);
@@ -1550,7 +1448,8 @@ hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone)
static VALUE
hash_alloc(VALUE klass)
{
- return hash_alloc_flags(klass, 0, Qnil);
+ /* Allocate to be able to fit both st_table and ar_table. */
+ return hash_alloc_flags(klass, 0, Qnil, sizeof(st_table) > sizeof(ar_table));
}
static VALUE
@@ -1576,19 +1475,16 @@ copy_compare_by_id(VALUE hash, VALUE basis)
return hash;
}
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_hash_new_with_size(st_index_t size)
{
- VALUE ret = rb_hash_new();
- if (size == 0) {
- /* do nothing */
- }
- else if (size <= RHASH_AR_TABLE_MAX_SIZE) {
- ar_alloc_table(ret);
- }
- else {
- RHASH_ST_TABLE_SET(ret, st_init_table_with_size(&objhash, size));
+ bool st = size > RHASH_AR_TABLE_MAX_SIZE;
+ VALUE ret = hash_alloc_flags(rb_cHash, 0, Qnil, st);
+
+ if (st) {
+ hash_st_table_init(ret, &objhash, size);
}
+
return ret;
}
@@ -1601,11 +1497,36 @@ rb_hash_new_capa(long capa)
static VALUE
hash_copy(VALUE ret, VALUE hash)
{
- if (!RHASH_EMPTY_P(hash)) {
- if (RHASH_AR_TABLE_P(hash))
+ if (rb_hash_compare_by_id_p(hash)) {
+ rb_gc_register_pinning_obj(ret);
+ }
+
+ if (RHASH_AR_TABLE_P(hash)) {
+ if (RHASH_AR_TABLE_P(ret)) {
ar_copy(ret, hash);
- else if (RHASH_ST_TABLE_P(hash))
- RHASH_ST_TABLE_SET(ret, st_copy(RHASH_ST_TABLE(hash)));
+ }
+ else {
+ st_table *tab = RHASH_ST_TABLE(ret);
+ st_init_existing_table_with_size(tab, &objhash, RHASH_AR_TABLE_SIZE(hash));
+
+ int bound = RHASH_AR_TABLE_BOUND(hash);
+ for (int i = 0; i < bound; i++) {
+ if (ar_cleared_entry(hash, i)) continue;
+
+ ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
+ st_add_direct(tab, pair->key, pair->val);
+ RB_OBJ_WRITTEN(ret, Qundef, pair->key);
+ RB_OBJ_WRITTEN(ret, Qundef, pair->val);
+ }
+ }
+ }
+ else {
+ HASH_ASSERT(sizeof(st_table) <= sizeof(ar_table));
+
+ RHASH_SET_ST_FLAG(ret);
+ st_replace(RHASH_ST_TABLE(ret), RHASH_ST_TABLE(hash));
+
+ rb_gc_writebarrier_remember(ret);
}
return ret;
}
@@ -1613,13 +1534,21 @@ hash_copy(VALUE ret, VALUE hash)
static VALUE
hash_dup_with_compare_by_id(VALUE hash)
{
- return hash_copy(copy_compare_by_id(rb_hash_new(), hash), hash);
+ VALUE dup = hash_alloc_flags(rb_cHash, 0, Qnil, RHASH_ST_TABLE_P(hash));
+ if (RHASH_ST_TABLE_P(hash)) {
+ RHASH_SET_ST_FLAG(dup);
+ }
+ else {
+ RHASH_UNSET_ST_FLAG(dup);
+ }
+
+ return hash_copy(dup, hash);
}
static VALUE
hash_dup(VALUE hash, VALUE klass, VALUE flags)
{
- return hash_copy(hash_alloc_flags(klass, flags, RHASH_IFNONE(hash)),
+ return hash_copy(hash_alloc_flags(klass, flags, RHASH_IFNONE(hash), !RHASH_EMPTY_P(hash) && RHASH_ST_TABLE_P(hash)),
hash);
}
@@ -1627,14 +1556,14 @@ VALUE
rb_hash_dup(VALUE hash)
{
const VALUE flags = RBASIC(hash)->flags;
- VALUE ret = hash_dup(hash, rb_obj_class(hash),
- flags & (FL_EXIVAR|RHASH_PROC_DEFAULT));
- if (flags & FL_EXIVAR)
- rb_copy_generic_ivar(ret, hash);
+ VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT);
+
+ rb_copy_generic_ivar(ret, hash);
+
return ret;
}
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_hash_resurrect(VALUE hash)
{
VALUE ret = hash_dup(hash, rb_cHash, 0);
@@ -1647,7 +1576,7 @@ rb_hash_modify_check(VALUE hash)
rb_check_frozen(hash);
}
-MJIT_FUNC_EXPORTED struct st_table *
+struct st_table *
rb_hash_tbl_raw(VALUE hash, const char *file, int line)
{
return ar_force_convert_table(hash, file, line);
@@ -1708,7 +1637,7 @@ rb_hash_stlike_update(VALUE hash, st_data_t key, st_update_callback_func *func,
if (RHASH_AR_TABLE_P(hash)) {
int result = ar_update(hash, key, func, arg);
if (result == -1) {
- ar_try_convert_table(hash);
+ ar_force_convert_table(hash, __FILE__, __LINE__);
}
else {
return result;
@@ -1753,26 +1682,26 @@ tbl_update(VALUE hash, VALUE key, tbl_update_func func, st_data_t optional_arg)
.func = func,
.hash = hash,
.key = key,
- .value = (VALUE)optional_arg,
+ .value = 0
};
int ret = rb_hash_stlike_update(hash, key, tbl_update_modify, (st_data_t)&arg);
/* write barrier */
RB_OBJ_WRITTEN(hash, Qundef, arg.key);
- RB_OBJ_WRITTEN(hash, Qundef, arg.value);
+ if (arg.value) RB_OBJ_WRITTEN(hash, Qundef, arg.value);
return ret;
}
-#define UPDATE_CALLBACK(iter_lev, func) ((iter_lev) > 0 ? func##_noinsert : func##_insert)
+#define UPDATE_CALLBACK(iter_p, func) ((iter_p) ? func##_noinsert : func##_insert)
-#define RHASH_UPDATE_ITER(h, iter_lev, key, func, a) do { \
- tbl_update((h), (key), UPDATE_CALLBACK((iter_lev), func), (st_data_t)(a)); \
+#define RHASH_UPDATE_ITER(h, iter_p, key, func, a) do { \
+ tbl_update((h), (key), UPDATE_CALLBACK(iter_p, func), (st_data_t)(a)); \
} while (0)
#define RHASH_UPDATE(hash, key, func, arg) \
- RHASH_UPDATE_ITER(hash, RHASH_ITER_LEV(hash), key, func, arg)
+ RHASH_UPDATE_ITER(hash, hash_iterating_p(hash), key, func, arg)
static void
set_proc_default(VALUE hash, VALUE proc)
@@ -1790,90 +1719,71 @@ set_proc_default(VALUE hash, VALUE proc)
RHASH_SET_IFNONE(hash, proc);
}
-/*
- * call-seq:
- * Hash.new(default_value = nil) -> new_hash
- * Hash.new {|hash, key| ... } -> new_hash
- *
- * Returns a new empty \Hash object.
- *
- * The initial default value and initial default proc for the new hash
- * depend on which form above was used. See {Default Values}[rdoc-ref:Hash@Default+Values].
- *
- * If neither an argument nor a block given,
- * initializes both the default value and the default proc to <tt>nil</tt>:
- * h = Hash.new
- * h.default # => nil
- * h.default_proc # => nil
- *
- * If argument <tt>default_value</tt> given but no block given,
- * initializes the default value to the given <tt>default_value</tt>
- * and the default proc to <tt>nil</tt>:
- * h = Hash.new(false)
- * h.default # => false
- * h.default_proc # => nil
- *
- * If a block given but no argument, stores the block as the default proc
- * and sets the default value to <tt>nil</tt>:
- * h = Hash.new {|hash, key| "Default value for #{key}" }
- * h.default # => nil
- * h.default_proc.class # => Proc
- * h[:nosuch] # => "Default value for nosuch"
- */
-
static VALUE
-rb_hash_initialize(int argc, VALUE *argv, VALUE hash)
+rb_hash_init(rb_execution_context_t *ec, VALUE hash, VALUE capa_value, VALUE ifnone_unset, VALUE ifnone, VALUE block)
{
- VALUE ifnone;
-
rb_hash_modify(hash);
- if (rb_block_given_p()) {
- rb_check_arity(argc, 0, 0);
- ifnone = rb_block_proc();
- SET_PROC_DEFAULT(hash, ifnone);
+
+ if (capa_value != INT2FIX(0)) {
+ long capa = NUM2LONG(capa_value);
+ if (capa > 0 && RHASH_SIZE(hash) == 0 && RHASH_AR_TABLE_P(hash)) {
+ hash_st_table_init(hash, &objhash, capa);
+ }
+ }
+
+ if (!NIL_P(block)) {
+ if (ifnone_unset != Qtrue) {
+ rb_check_arity(1, 0, 0);
+ }
+ else {
+ SET_PROC_DEFAULT(hash, block);
+ }
}
else {
- rb_check_arity(argc, 0, 1);
- ifnone = argc == 0 ? Qnil : argv[0];
- RHASH_SET_IFNONE(hash, ifnone);
+ RHASH_SET_IFNONE(hash, ifnone_unset == Qtrue ? Qnil : ifnone);
}
+ hash_verify(hash);
return hash;
}
+static VALUE rb_hash_to_a(VALUE hash);
+
/*
* call-seq:
* Hash[] -> new_empty_hash
- * Hash[hash] -> new_hash
+ * Hash[other_hash] -> new_hash
* Hash[ [*2_element_arrays] ] -> new_hash
* Hash[*objects] -> new_hash
*
* Returns a new \Hash object populated with the given objects, if any.
* See Hash::new.
*
- * With no argument, returns a new empty \Hash.
+ * With no argument given, returns a new empty hash.
*
- * When the single given argument is a \Hash, returns a new \Hash
- * populated with the entries from the given \Hash, excluding the
- * default value or proc.
+ * With a single argument +other_hash+ given that is a hash,
+ * returns a new hash initialized with the entries from that hash
+ * (but not with its +default+ or +default_proc+):
*
* h = {foo: 0, bar: 1, baz: 2}
- * Hash[h] # => {:foo=>0, :bar=>1, :baz=>2}
+ * Hash[h] # => {foo: 0, bar: 1, baz: 2}
*
- * When the single given argument is an \Array of 2-element Arrays,
- * returns a new \Hash object wherein each 2-element array forms a
+ * With a single argument +2_element_arrays+ given that is an array of 2-element arrays,
+ * returns a new hash wherein each given 2-element array forms a
* key-value entry:
*
- * Hash[ [ [:foo, 0], [:bar, 1] ] ] # => {:foo=>0, :bar=>1}
+ * Hash[ [ [:foo, 0], [:bar, 1] ] ] # => {foo: 0, bar: 1}
*
- * When the argument count is an even number;
- * returns a new \Hash object wherein each successive pair of arguments
- * has become a key-value entry:
+ * With an even number of arguments +objects+ given,
+ * returns a new hash wherein each successive pair of arguments
+ * is a key-value entry:
*
- * Hash[:foo, 0, :bar, 1] # => {:foo=>0, :bar=>1}
+ * Hash[:foo, 0, :bar, 1] # => {foo: 0, bar: 1}
*
- * Raises an exception if the argument list does not conform to any
+ * Raises ArgumentError if the argument list does not conform to any
* of the above.
+ *
+ * See also {Methods for Creating a Hash}[rdoc-ref:Hash@Methods+for+Creating+a+Hash].
*/
static VALUE
@@ -1884,12 +1794,23 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
if (argc == 1) {
tmp = rb_hash_s_try_convert(Qnil, argv[0]);
if (!NIL_P(tmp)) {
- hash = hash_alloc(klass);
- hash_copy(hash, tmp);
- return hash;
+ if (!RHASH_EMPTY_P(tmp) && rb_hash_compare_by_id_p(tmp)) {
+ /* hash_copy for non-empty hash will copy compare_by_identity
+ flag, but we don't want it copied. Work around by
+ converting hash to flattened array and using that. */
+ tmp = rb_hash_to_a(tmp);
+ }
+ else {
+ hash = hash_alloc(klass);
+ if (!RHASH_EMPTY_P(tmp))
+ hash_copy(hash, tmp);
+ return hash;
+ }
+ }
+ else {
+ tmp = rb_check_array_type(argv[0]);
}
- tmp = rb_check_array_type(argv[0]);
if (!NIL_P(tmp)) {
long i;
@@ -1927,7 +1848,7 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
return hash;
}
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_to_hash_type(VALUE hash)
{
return rb_convert_type_with_id(hash, T_HASH, "Hash", idTo_hash);
@@ -1942,16 +1863,15 @@ rb_check_hash_type(VALUE hash)
/*
* call-seq:
- * Hash.try_convert(obj) -> obj, new_hash, or nil
- *
- * If +obj+ is a \Hash object, returns +obj+.
+ * Hash.try_convert(object) -> object, new_hash, or nil
*
- * Otherwise if +obj+ responds to <tt>:to_hash</tt>,
- * calls <tt>obj.to_hash</tt> and returns the result.
+ * If +object+ is a hash, returns +object+.
*
- * Returns +nil+ if +obj+ does not respond to <tt>:to_hash</tt>
+ * Otherwise if +object+ responds to +:to_hash+,
+ * calls <tt>object.to_hash</tt>;
+ * returns the result if it is a hash, or raises TypeError if not.
*
- * Raises an exception unless <tt>obj.to_hash</tt> returns a \Hash object.
+ * Otherwise if +object+ does not respond to +:to_hash+, returns +nil+.
*/
static VALUE
rb_hash_s_try_convert(VALUE dummy, VALUE hash)
@@ -2000,9 +1920,12 @@ static VALUE
rb_hash_s_ruby2_keywords_hash(VALUE dummy, VALUE hash)
{
Check_Type(hash, T_HASH);
- hash = rb_hash_dup(hash);
- RHASH(hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
- return hash;
+ VALUE tmp = rb_hash_dup(hash);
+ if (RHASH_EMPTY_P(hash) && rb_hash_compare_by_id_p(hash)) {
+ rb_hash_compare_by_id(tmp);
+ }
+ RHASH(tmp)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
+ return tmp;
}
struct rehash_arg {
@@ -2024,10 +1947,11 @@ rb_hash_rehash_i(VALUE key, VALUE value, VALUE arg)
/*
* call-seq:
- * hash.rehash -> self
+ * rehash -> self
*
- * Rebuilds the hash table by recomputing the hash index for each key;
+ * Rebuilds the hash table for +self+ by recomputing the hash index for each key;
* returns <tt>self</tt>.
+ * Calling this method ensures that the hash table is valid.
*
* The hash table becomes invalid if the hash value of a key
* has changed after the entry was created.
@@ -2040,25 +1964,27 @@ rb_hash_rehash(VALUE hash)
VALUE tmp;
st_table *tbl;
- if (RHASH_ITER_LEV(hash) > 0) {
+ if (hash_iterating_p(hash)) {
rb_raise(rb_eRuntimeError, "rehash during iteration");
}
rb_hash_modify_check(hash);
if (RHASH_AR_TABLE_P(hash)) {
tmp = hash_alloc(0);
- ar_alloc_table(tmp);
rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp);
- ar_free_and_clear_table(hash);
+
+ hash_ar_free_and_clear_table(hash);
ar_copy(hash, tmp);
- ar_free_and_clear_table(tmp);
}
else if (RHASH_ST_TABLE_P(hash)) {
st_table *old_tab = RHASH_ST_TABLE(hash);
tmp = hash_alloc(0);
- tbl = st_init_table_with_size(old_tab->type, old_tab->num_entries);
- RHASH_ST_TABLE_SET(tmp, tbl);
+
+ hash_st_table_init(tmp, old_tab->type, old_tab->num_entries);
+ tbl = RHASH_ST_TABLE(tmp);
+
rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp);
- st_free_table(old_tab);
+
+ hash_st_free(hash);
RHASH_ST_TABLE_SET(hash, tbl);
RHASH_ST_CLEAR(tmp);
}
@@ -2073,7 +1999,7 @@ call_default_proc(VALUE proc, VALUE hash, VALUE key)
return rb_proc_call_with_block(proc, 2, args, Qnil);
}
-static bool
+bool
rb_hash_default_unredefined(VALUE hash)
{
VALUE klass = RBASIC_CLASS(hash);
@@ -2118,7 +2044,7 @@ hash_stlike_lookup(VALUE hash, st_data_t key, st_data_t *pval)
}
}
-MJIT_FUNC_EXPORTED int
+int
rb_hash_stlike_lookup(VALUE hash, st_data_t key, st_data_t *pval)
{
return hash_stlike_lookup(hash, key, pval);
@@ -2126,16 +2052,19 @@ rb_hash_stlike_lookup(VALUE hash, st_data_t key, st_data_t *pval)
/*
* call-seq:
- * hash[key] -> value
+ * self[key] -> object
*
- * Returns the value associated with the given +key+, if found:
- * h = {foo: 0, bar: 1, baz: 2}
- * h[:foo] # => 0
+ * Searches for a hash key equivalent to the given +key+;
+ * see {Hash Key Equivalence}[rdoc-ref:Hash@Hash+Key+Equivalence].
*
- * If +key+ is not found, returns a default value
- * (see {Default Values}[rdoc-ref:Hash@Default+Values]):
- * h = {foo: 0, bar: 1, baz: 2}
- * h[:nosuch] # => nil
+ * If the key is found, returns its value:
+ *
+ * {foo: 0, bar: 1, baz: 2}
+ * h[:bar] # => 1
+ *
+ * Otherwise, returns a default value (see {Hash Default}[rdoc-ref:Hash@Hash+Default]).
+ *
+ * Related: #[]=; see also {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
VALUE
@@ -2172,25 +2101,28 @@ rb_hash_lookup(VALUE hash, VALUE key)
/*
* call-seq:
- * hash.fetch(key) -> object
- * hash.fetch(key, default_value) -> object
- * hash.fetch(key) {|key| ... } -> object
+ * fetch(key) -> object
+ * fetch(key, default_value) -> object
+ * fetch(key) {|key| ... } -> object
+ *
+ * With no block given, returns the value for the given +key+, if found;
*
- * Returns the value for the given +key+, if found.
* h = {foo: 0, bar: 1, baz: 2}
- * h.fetch(:bar) # => 1
+ * h.fetch(:bar) # => 1
*
- * If +key+ is not found and no block was given,
- * returns +default_value+:
- * {}.fetch(:nosuch, :default) # => :default
+ * If the key is not found, returns +default_value+, if given,
+ * or raises KeyError otherwise:
*
- * If +key+ is not found and a block was given,
- * yields +key+ to the block and returns the block's return value:
- * {}.fetch(:nosuch) {|key| "No key #{key}"} # => "No key nosuch"
+ * h.fetch(:nosuch, :default) # => :default
+ * h.fetch(:nosuch) # Raises KeyError.
*
- * Raises KeyError if neither +default_value+ nor a block was given.
+ * With a block given, calls the block with +key+ and returns the block's return value:
+ *
+ * {}.fetch(:nosuch) {|key| "No key #{key}"} # => "No key nosuch"
*
* Note that this method does not use the values of either #default or #default_proc.
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -2237,12 +2169,12 @@ rb_hash_fetch(VALUE hash, VALUE key)
/*
* call-seq:
- * hash.default -> object
- * hash.default(key) -> object
+ * default -> object
+ * default(key) -> object
*
* Returns the default value for the given +key+.
* The returned value will be determined either by the default proc or by the default value.
- * See {Default Values}[rdoc-ref:Hash@Default+Values].
+ * See {Hash Default}[rdoc-ref:Hash@Hash+Default].
*
* With no argument, returns the current default value:
* h = {}
@@ -2271,7 +2203,7 @@ rb_hash_default(int argc, VALUE *argv, VALUE hash)
/*
* call-seq:
- * hash.default = value -> object
+ * default = value -> object
*
* Sets the default value to +value+; returns +value+:
* h = {}
@@ -2279,10 +2211,10 @@ rb_hash_default(int argc, VALUE *argv, VALUE hash)
* h.default = false # => false
* h.default # => false
*
- * See {Default Values}[rdoc-ref:Hash@Default+Values].
+ * See {Hash Default}[rdoc-ref:Hash@Hash+Default].
*/
-static VALUE
+VALUE
rb_hash_set_default(VALUE hash, VALUE ifnone)
{
rb_hash_modify_check(hash);
@@ -2292,10 +2224,10 @@ rb_hash_set_default(VALUE hash, VALUE ifnone)
/*
* call-seq:
- * hash.default_proc -> proc or nil
+ * default_proc -> proc or nil
*
* Returns the default proc for +self+
- * (see {Default Values}[rdoc-ref:Hash@Default+Values]):
+ * (see {Hash Default}[rdoc-ref:Hash@Hash+Default]):
* h = {}
* h.default_proc # => nil
* h.default_proc = proc {|hash, key| "Default value for #{key}" }
@@ -2313,10 +2245,10 @@ rb_hash_default_proc(VALUE hash)
/*
* call-seq:
- * hash.default_proc = proc -> proc
+ * default_proc = proc -> proc
*
- * Sets the default proc for +self+ to +proc+:
- * (see {Default Values}[rdoc-ref:Hash@Default+Values]):
+ * Sets the default proc for +self+ to +proc+
+ * (see {Hash Default}[rdoc-ref:Hash@Hash+Default]):
* h = {}
* h.default_proc # => nil
* h.default_proc = proc { |hash, key| "Default value for #{key}" }
@@ -2360,15 +2292,18 @@ key_i(VALUE key, VALUE value, VALUE arg)
/*
* call-seq:
- * hash.key(value) -> key or nil
+ * key(value) -> key or nil
*
* Returns the key for the first-found entry with the given +value+
* (see {Entry Order}[rdoc-ref:Hash@Entry+Order]):
+ *
* h = {foo: 0, bar: 2, baz: 2}
* h.key(0) # => :foo
* h.key(2) # => :bar
*
- * Returns +nil+ if so such value is found.
+ * Returns +nil+ if no such value is found.
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -2433,29 +2368,36 @@ rb_hash_delete(VALUE hash, VALUE key)
/*
* call-seq:
- * hash.delete(key) -> value or nil
- * hash.delete(key) {|key| ... } -> object
+ * delete(key) -> value or nil
+ * delete(key) {|key| ... } -> object
*
- * Deletes the entry for the given +key+ and returns its associated value.
+ * If an entry for the given +key+ is found,
+ * deletes the entry and returns its associated value;
+ * otherwise returns +nil+ or calls the given block.
+ *
+ * With no block given and +key+ found, deletes the entry and returns its value:
*
- * If no block is given and +key+ is found, deletes the entry and returns the associated value:
* h = {foo: 0, bar: 1, baz: 2}
* h.delete(:bar) # => 1
- * h # => {:foo=>0, :baz=>2}
+ * h # => {foo: 0, baz: 2}
+ *
+ * With no block given and +key+ not found, returns +nil+.
*
- * If no block given and +key+ is not found, returns +nil+.
+ * With a block given and +key+ found, ignores the block,
+ * deletes the entry, and returns its value:
*
- * If a block is given and +key+ is found, ignores the block,
- * deletes the entry, and returns the associated value:
* h = {foo: 0, bar: 1, baz: 2}
* h.delete(:baz) { |key| raise 'Will never happen'} # => 2
- * h # => {:foo=>0, :bar=>1}
+ * h # => {foo: 0, bar: 1}
*
- * If a block is given and +key+ is not found,
+ * With a block given and +key+ not found,
* calls the block and returns the block's return value:
+ *
* h = {foo: 0, bar: 1, baz: 2}
* h.delete(:nosuch) { |key| "Key #{key} not found" } # => "Key nosuch not found"
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2467,6 +2409,7 @@ rb_hash_delete_m(VALUE hash, VALUE key)
val = rb_hash_delete_entry(hash, key);
if (!UNDEF_P(val)) {
+ compact_after_delete(hash);
return val;
}
else {
@@ -2496,16 +2439,18 @@ shift_i_safe(VALUE key, VALUE value, VALUE arg)
/*
* call-seq:
- * hash.shift -> [key, value] or nil
+ * shift -> [key, value] or nil
+ *
+ * Removes and returns the first entry of +self+ as a 2-element array;
+ * see {Entry Order}[rdoc-ref:Hash@Entry+Order]:
*
- * Removes the first hash entry
- * (see {Entry Order}[rdoc-ref:Hash@Entry+Order]);
- * returns a 2-element \Array containing the removed key and value:
* h = {foo: 0, bar: 1, baz: 2}
* h.shift # => [:foo, 0]
- * h # => {:bar=>1, :baz=>2}
+ * h # => {bar: 1, baz: 2}
*
- * Returns nil if the hash is empty.
+ * Returns +nil+ if +self+ is empty.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2516,7 +2461,7 @@ rb_hash_shift(VALUE hash)
rb_hash_modify_check(hash);
if (RHASH_AR_TABLE_P(hash)) {
var.key = Qundef;
- if (RHASH_ITER_LEV(hash) == 0) {
+ if (!hash_iterating_p(hash)) {
if (ar_shift(hash, &var.key, &var.val)) {
return rb_assoc_new(var.key, var.val);
}
@@ -2531,7 +2476,7 @@ rb_hash_shift(VALUE hash)
}
if (RHASH_ST_TABLE_P(hash)) {
var.key = Qundef;
- if (RHASH_ITER_LEV(hash) == 0) {
+ if (!hash_iterating_p(hash)) {
if (st_shift(RHASH_ST_TABLE(hash), &var.key, &var.val)) {
return rb_assoc_new(var.key, var.val);
}
@@ -2565,19 +2510,19 @@ hash_enum_size(VALUE hash, VALUE args, VALUE eobj)
/*
* call-seq:
- * hash.delete_if {|key, value| ... } -> self
- * hash.delete_if -> new_enumerator
+ * delete_if {|key, value| ... } -> self
+ * delete_if -> new_enumerator
*
- * If a block given, calls the block with each key-value pair;
- * deletes each entry for which the block returns a truthy value;
- * returns +self+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.delete_if {|key, value| value > 0 } # => {:foo=>0}
+ * With a block given, calls the block with each key-value pair,
+ * deletes each entry for which the block returns a truthy value,
+ * and returns +self+:
*
- * If no block given, returns a new \Enumerator:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.delete_if # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:delete_if>
- * e.each { |key, value| value > 0 } # => {:foo=>0}
+ * h.delete_if {|key, value| value > 0 } # => {foo: 0}
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
VALUE
@@ -2587,26 +2532,28 @@ rb_hash_delete_if(VALUE hash)
rb_hash_modify_check(hash);
if (!RHASH_TABLE_EMPTY_P(hash)) {
rb_hash_foreach(hash, delete_if_i, hash);
+ compact_after_delete(hash);
}
return hash;
}
/*
* call-seq:
- * hash.reject! {|key, value| ... } -> self or nil
- * hash.reject! -> new_enumerator
+ * reject! {|key, value| ... } -> self or nil
+ * reject! -> new_enumerator
*
- * Returns +self+, whose remaining entries are those
- * for which the block returns +false+ or +nil+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.reject! {|key, value| value < 2 } # => {:baz=>2}
+ * With a block given, calls the block with each entry's key and value;
+ * removes the entry from +self+ if the block returns a truthy value.
*
- * Returns +nil+ if no entries are removed.
+ * Return +self+ if any entries were removed, +nil+ otherwise:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.reject! # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:reject!>
- * e.each {|key, value| key.start_with?('b') } # => {:foo=>0}
+ * h.reject! {|key, value| value < 2 } # => {baz: 2}
+ * h.reject! {|key, value| value < 2 } # => nil
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2625,20 +2572,21 @@ rb_hash_reject_bang(VALUE hash)
/*
* call-seq:
- * hash.reject {|key, value| ... } -> new_hash
- * hash.reject -> new_enumerator
+ * reject {|key, value| ... } -> new_hash
+ * reject -> new_enumerator
*
- * Returns a new \Hash object whose entries are all those
- * from +self+ for which the block returns +false+ or +nil+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h1 = h.reject {|key, value| key.start_with?('b') }
- * h1 # => {:foo=>0}
+ * With a block given, returns a copy of +self+ with zero or more entries removed;
+ * calls the block with each key-value pair;
+ * excludes the entry in the copy if the block returns a truthy value,
+ * includes it otherwise:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.reject # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:reject>
- * h1 = e.each {|key, value| key.start_with?('b') }
- * h1 # => {:foo=>0}
+ * h.reject {|key, value| key.start_with?('b') }
+ * # => {foo: 0}
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2650,19 +2598,22 @@ rb_hash_reject(VALUE hash)
result = hash_dup_with_compare_by_id(hash);
if (!RHASH_EMPTY_P(hash)) {
rb_hash_foreach(result, delete_if_i, result);
+ compact_after_delete(result);
}
return result;
}
/*
* call-seq:
- * hash.slice(*keys) -> new_hash
+ * slice(*keys) -> new_hash
+ *
+ * Returns a new hash containing the entries from +self+ for the given +keys+;
+ * ignores any keys that are not found:
*
- * Returns a new \Hash object containing the entries for the given +keys+:
* h = {foo: 0, bar: 1, baz: 2}
- * h.slice(:baz, :foo) # => {:baz=>2, :foo=>0}
+ * h.slice(:baz, :foo, :nosuch) # => {baz: 2, foo: 0}
*
- * Any given +keys+ that are not found are ignored.
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2688,13 +2639,16 @@ rb_hash_slice(int argc, VALUE *argv, VALUE hash)
/*
* call-seq:
- * hsh.except(*keys) -> a_hash
+ * except(*keys) -> new_hash
+ *
+ * Returns a copy of +self+ that excludes entries for the given +keys+;
+ * any +keys+ that are not found are ignored:
*
- * Returns a new \Hash excluding entries for the given +keys+:
- * h = { a: 100, b: 200, c: 300 }
- * h.except(:a) #=> {:b=>200, :c=>300}
+ * h = {foo:0, bar: 1, baz: 2} # => {:foo=>0, :bar=>1, :baz=>2}
+ * h.except(:baz, :foo) # => {:bar=>1}
+ * h.except(:bar, :nosuch) # => {:foo=>0, :baz=>2}
*
- * Any given +keys+ that are not found are ignored.
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2709,21 +2663,26 @@ rb_hash_except(int argc, VALUE *argv, VALUE hash)
key = argv[i];
rb_hash_delete(result, key);
}
+ compact_after_delete(result);
return result;
}
/*
* call-seq:
- * hash.values_at(*keys) -> new_array
+ * values_at(*keys) -> new_array
+ *
+ * Returns a new array containing values for the given +keys+:
*
- * Returns a new \Array containing values for the given +keys+:
* h = {foo: 0, bar: 1, baz: 2}
* h.values_at(:baz, :foo) # => [2, 0]
*
- * The {default values}[rdoc-ref:Hash@Default+Values] are returned
- * for any keys that are not found:
+ * The {hash default}[rdoc-ref:Hash@Hash+Default] is returned
+ * for each key that is not found:
+ *
* h.values_at(:hello, :foo) # => [nil, 0]
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -2740,22 +2699,26 @@ rb_hash_values_at(int argc, VALUE *argv, VALUE hash)
/*
* call-seq:
- * hash.fetch_values(*keys) -> new_array
- * hash.fetch_values(*keys) {|key| ... } -> new_array
+ * fetch_values(*keys) -> new_array
+ * fetch_values(*keys) {|key| ... } -> new_array
+ *
+ * When all given +keys+ are found,
+ * returns a new array containing the values associated with the given +keys+:
*
- * Returns a new \Array containing the values associated with the given keys *keys:
* h = {foo: 0, bar: 1, baz: 2}
* h.fetch_values(:baz, :foo) # => [2, 0]
*
- * Returns a new empty \Array if no arguments given.
+ * When any given +keys+ are not found and a block is given,
+ * calls the block with each unfound key and uses the block's return value
+ * as the value for that key:
*
- * When a block is given, calls the block with each missing key,
- * treating the block's return value as the value for that key:
- * h = {foo: 0, bar: 1, baz: 2}
- * values = h.fetch_values(:bar, :foo, :bad, :bam) {|key| key.to_s}
- * values # => [1, 0, "bad", "bam"]
+ * h.fetch_values(:bar, :foo, :bad, :bam) {|key| key.to_s}
+ * # => [1, 0, "bad", "bam"]
+ *
+ * When any given +keys+ are not found and no block is given,
+ * raises KeyError.
*
- * When no block is given, raises an exception if any given key is not found.
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -2782,19 +2745,18 @@ keep_if_i(VALUE key, VALUE value, VALUE hash)
/*
* call-seq:
- * hash.select {|key, value| ... } -> new_hash
- * hash.select -> new_enumerator
+ * select {|key, value| ... } -> new_hash
+ * select -> new_enumerator
*
- * Hash#filter is an alias for Hash#select.
+ * With a block given, calls the block with each entry's key and value;
+ * returns a new hash whose entries are those for which the block returns a truthy value:
*
- * Returns a new \Hash object whose entries are those for which the block returns a truthy value:
* h = {foo: 0, bar: 1, baz: 2}
- * h.select {|key, value| value < 2 } # => {:foo=>0, :bar=>1}
+ * h.select {|key, value| value < 2 } # => {foo: 0, bar: 1}
*
- * Returns a new \Enumerator if no block given:
- * h = {foo: 0, bar: 1, baz: 2}
- * e = h.select # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:select>
- * e.each {|key, value| value < 2 } # => {:foo=>0, :bar=>1}
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2806,27 +2768,29 @@ rb_hash_select(VALUE hash)
result = hash_dup_with_compare_by_id(hash);
if (!RHASH_EMPTY_P(hash)) {
rb_hash_foreach(result, keep_if_i, result);
+ compact_after_delete(result);
}
return result;
}
/*
* call-seq:
- * hash.select! {|key, value| ... } -> self or nil
- * hash.select! -> new_enumerator
+ * select! {|key, value| ... } -> self or nil
+ * select! -> new_enumerator
*
- * Hash#filter! is an alias for Hash#select!.
+ * With a block given, calls the block with each entry's key and value;
+ * removes from +self+ each entry for which the block returns +false+ or +nil+.
+ *
+ * Returns +self+ if any entries were removed, +nil+ otherwise:
*
- * Returns +self+, whose entries are those for which the block returns a truthy value:
* h = {foo: 0, bar: 1, baz: 2}
- * h.select! {|key, value| value < 2 } => {:foo=>0, :bar=>1}
+ * h.select! {|key, value| value < 2 } # => {foo: 0, bar: 1}
+ * h.select! {|key, value| value < 2 } # => nil
*
- * Returns +nil+ if no entries were removed.
*
- * Returns a new \Enumerator if no block given:
- * h = {foo: 0, bar: 1, baz: 2}
- * e = h.select! # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:select!>
- * e.each { |key, value| value < 2 } # => {:foo=>0, :bar=>1}
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2845,19 +2809,19 @@ rb_hash_select_bang(VALUE hash)
/*
* call-seq:
- * hash.keep_if {|key, value| ... } -> self
- * hash.keep_if -> new_enumerator
+ * keep_if {|key, value| ... } -> self
+ * keep_if -> new_enumerator
*
- * Calls the block for each key-value pair;
+ * With a block given, calls the block for each key-value pair;
* retains the entry if the block returns a truthy value;
- * otherwise deletes the entry; returns +self+.
- * h = {foo: 0, bar: 1, baz: 2}
- * h.keep_if { |key, value| key.start_with?('b') } # => {:bar=>1, :baz=>2}
+ * otherwise deletes the entry; returns +self+:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.keep_if # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:keep_if>
- * e.each { |key, value| key.start_with?('b') } # => {:bar=>1, :baz=>2}
+ * h.keep_if { |key, value| key.start_with?('b') } # => {bar: 1, baz: 2}
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -2879,9 +2843,11 @@ clear_i(VALUE key, VALUE value, VALUE dummy)
/*
* call-seq:
- * hash.clear -> self
+ * clear -> self
*
- * Removes all hash entries; returns +self+.
+ * Removes all entries from +self+; returns emptied +self+.
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
VALUE
@@ -2889,7 +2855,7 @@ rb_hash_clear(VALUE hash)
{
rb_hash_modify_check(hash);
- if (RHASH_ITER_LEV(hash) > 0) {
+ if (hash_iterating_p(hash)) {
rb_hash_foreach(hash, clear_i, 0);
}
else if (RHASH_AR_TABLE_P(hash)) {
@@ -2897,6 +2863,7 @@ rb_hash_clear(VALUE hash)
}
else {
st_clear(RHASH_ST_TABLE(hash));
+ compact_after_delete(hash);
}
return hash;
@@ -2912,7 +2879,7 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
VALUE
rb_hash_key_str(VALUE key)
{
- if (!RB_FL_ANY_RAW(key, FL_EXIVAR) && RBASIC_CLASS(key) == rb_cString) {
+ if (!rb_obj_gen_fields_p(key) && RBASIC_CLASS(key) == rb_cString) {
return rb_fstring(key);
}
else {
@@ -2934,59 +2901,68 @@ NOINSERT_UPDATE_CALLBACK(hash_aset_str)
/*
* call-seq:
- * hash[key] = value -> value
- * hash.store(key, value)
+ * self[key] = object -> object
*
- * Hash#store is an alias for Hash#[]=.
-
- * Associates the given +value+ with the given +key+; returns +value+.
+ * Associates the given +object+ with the given +key+; returns +object+.
+ *
+ * Searches for a hash key equivalent to the given +key+;
+ * see {Hash Key Equivalence}[rdoc-ref:Hash@Hash+Key+Equivalence].
*
- * If the given +key+ exists, replaces its value with the given +value+;
+ * If the key is found, replaces its value with the given +object+;
* the ordering is not affected
* (see {Entry Order}[rdoc-ref:Hash@Entry+Order]):
+ *
* h = {foo: 0, bar: 1}
* h[:foo] = 2 # => 2
- * h.store(:bar, 3) # => 3
- * h # => {:foo=>2, :bar=>3}
+ * h[:foo] # => 2
*
- * If +key+ does not exist, adds the +key+ and +value+;
+ * If +key+ is not found, creates a new entry for the given +key+ and +object+;
* the new entry is last in the order
* (see {Entry Order}[rdoc-ref:Hash@Entry+Order]):
+ *
* h = {foo: 0, bar: 1}
* h[:baz] = 2 # => 2
- * h.store(:bat, 3) # => 3
- * h # => {:foo=>0, :bar=>1, :baz=>2, :bat=>3}
+ * h[:baz] # => 2
+ * h # => {foo: 0, bar: 1, baz: 2}
+ *
+ * Related: #[]; see also {Methods for Assigning}[rdoc-ref:Hash@Methods+for+Assigning].
*/
VALUE
rb_hash_aset(VALUE hash, VALUE key, VALUE val)
{
- int iter_lev = RHASH_ITER_LEV(hash);
+ bool iter_p = hash_iterating_p(hash);
rb_hash_modify(hash);
- if (RHASH_TABLE_NULL_P(hash)) {
- if (iter_lev > 0) no_new_key();
- ar_alloc_table(hash);
- }
-
- if (RHASH_TYPE(hash) == &identhash || rb_obj_class(key) != rb_cString) {
- RHASH_UPDATE_ITER(hash, iter_lev, key, hash_aset, val);
+ if (!RHASH_STRING_KEY_P(hash, key)) {
+ RHASH_UPDATE_ITER(hash, iter_p, key, hash_aset, val);
}
else {
- RHASH_UPDATE_ITER(hash, iter_lev, key, hash_aset_str, val);
+ RHASH_UPDATE_ITER(hash, iter_p, key, hash_aset_str, val);
}
return val;
}
/*
* call-seq:
- * hash.replace(other_hash) -> self
+ * replace(other_hash) -> self
*
* Replaces the entire contents of +self+ with the contents of +other_hash+;
* returns +self+:
+ *
* h = {foo: 0, bar: 1, baz: 2}
- * h.replace({bat: 3, bam: 4}) # => {:bat=>3, :bam=>4}
+ * h.replace({bat: 3, bam: 4}) # => {bat: 3, bam: 4}
+ *
+ * Also replaces the default value or proc of +self+ with the default value
+ * or proc of +other_hash+.
+ *
+ * h = {}
+ * other = Hash.new(:ok)
+ * h.replace(other)
+ * h.default # => :ok
+ *
+ * Related: see {Methods for Assigning}[rdoc-ref:Hash@Methods+for+Assigning].
*/
static VALUE
@@ -2994,7 +2970,7 @@ rb_hash_replace(VALUE hash, VALUE hash2)
{
rb_hash_modify_check(hash);
if (hash == hash2) return hash;
- if (RHASH_ITER_LEV(hash) > 0) {
+ if (hash_iterating_p(hash)) {
rb_raise(rb_eRuntimeError, "can't replace hash during iteration");
}
hash2 = to_hash(hash2);
@@ -3002,32 +2978,26 @@ rb_hash_replace(VALUE hash, VALUE hash2)
COPY_DEFAULT(hash, hash2);
if (RHASH_AR_TABLE_P(hash)) {
- ar_free_and_clear_table(hash);
+ hash_ar_free_and_clear_table(hash);
}
else {
- st_free_table(RHASH_ST_TABLE(hash));
- RHASH_ST_CLEAR(hash);
- }
- hash_copy(hash, hash2);
- if (RHASH_EMPTY_P(hash2) && RHASH_ST_TABLE_P(hash2)) {
- /* ident hash */
- RHASH_ST_TABLE_SET(hash, st_init_table_with_size(RHASH_TYPE(hash2), 0));
+ hash_st_free_and_clear_table(hash);
}
- rb_gc_writebarrier_remember(hash);
+ hash_copy(hash, hash2);
return hash;
}
/*
* call-seq:
- * hash.length -> integer
- * hash.size -> integer
+ * size -> integer
*
* Returns the count of entries in +self+:
- * {foo: 0, bar: 1, baz: 2}.length # => 3
*
- * Hash#length is an alias for Hash#size.
+ * {foo: 0, bar: 1, baz: 2}.size # => 3
+ *
+ * Related: see {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
VALUE
@@ -3044,14 +3014,17 @@ rb_hash_size_num(VALUE hash)
/*
* call-seq:
- * hash.empty? -> true or false
+ * empty? -> true or false
*
* Returns +true+ if there are no hash entries, +false+ otherwise:
+ *
* {}.empty? # => true
- * {foo: 0, bar: 1, baz: 2}.empty? # => false
+ * {foo: 0}.empty? # => false
+ *
+ * Related: see {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
-static VALUE
+VALUE
rb_hash_empty_p(VALUE hash)
{
return RBOOL(RHASH_EMPTY_P(hash));
@@ -3066,26 +3039,22 @@ each_value_i(VALUE key, VALUE value, VALUE _)
/*
* call-seq:
- * hash.each_value {|value| ... } -> self
- * hash.each_value -> new_enumerator
+ * each_value {|value| ... } -> self
+ * each_value -> new_enumerator
*
- * Calls the given block with each value; returns +self+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.each_value {|value| puts value } # => {:foo=>0, :bar=>1, :baz=>2}
- * Output:
- * 0
- * 1
- * 2
+ * With a block given, calls the block with each value; returns +self+:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.each_value # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each_value>
- * h1 = e.each {|value| puts value }
- * h1 # => {:foo=>0, :bar=>1, :baz=>2}
+ * h.each_value {|value| puts value } # => {foo: 0, bar: 1, baz: 2}
+ *
* Output:
* 0
* 1
* 2
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Iterating}[rdoc-ref:Hash@Methods+for+Iterating].
*/
static VALUE
@@ -3105,26 +3074,22 @@ each_key_i(VALUE key, VALUE value, VALUE _)
/*
* call-seq:
- * hash.each_key {|key| ... } -> self
- * hash.each_key -> new_enumerator
+ * each_key {|key| ... } -> self
+ * each_key -> new_enumerator
*
- * Calls the given block with each key; returns +self+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.each_key {|key| puts key } # => {:foo=>0, :bar=>1, :baz=>2}
- * Output:
- * foo
- * bar
- * baz
+ * With a block given, calls the block with each key; returns +self+:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.each_key # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each_key>
- * h1 = e.each {|key| puts key }
- * h1 # => {:foo=>0, :bar=>1, :baz=>2}
+ * h.each_key {|key| puts key } # => {foo: 0, bar: 1, baz: 2}
+ *
* Output:
* foo
* bar
* baz
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Iterating}[rdoc-ref:Hash@Methods+for+Iterating].
*/
static VALUE
rb_hash_each_key(VALUE hash)
@@ -3153,30 +3118,23 @@ each_pair_i_fast(VALUE key, VALUE value, VALUE _)
/*
* call-seq:
- * hash.each {|key, value| ... } -> self
- * hash.each_pair {|key, value| ... } -> self
- * hash.each -> new_enumerator
- * hash.each_pair -> new_enumerator
+ * each_pair {|key, value| ... } -> self
+ * each_pair -> new_enumerator
*
- * Hash#each is an alias for Hash#each_pair.
-
- * Calls the given block with each key-value pair; returns +self+:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.each_pair {|key, value| puts "#{key}: #{value}"} # => {:foo=>0, :bar=>1, :baz=>2}
- * Output:
- * foo: 0
- * bar: 1
- * baz: 2
+ * With a block given, calls the block with each key-value pair; returns +self+:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.each_pair # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:each_pair>
- * h1 = e.each {|key, value| puts "#{key}: #{value}"}
- * h1 # => {:foo=>0, :bar=>1, :baz=>2}
+ * h.each_pair {|key, value| puts "#{key}: #{value}"} # => {foo: 0, bar: 1, baz: 2}
+ *
* Output:
+ *
* foo: 0
* bar: 1
* baz: 2
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Iterating}[rdoc-ref:Hash@Methods+for+Iterating].
*/
static VALUE
@@ -3222,40 +3180,91 @@ transform_keys_i(VALUE key, VALUE value, VALUE result)
/*
* call-seq:
- * hash.transform_keys {|key| ... } -> new_hash
- * hash.transform_keys(hash2) -> new_hash
- * hash.transform_keys(hash2) {|other_key| ...} -> new_hash
- * hash.transform_keys -> new_enumerator
+ * transform_keys {|old_key| ... } -> new_hash
+ * transform_keys(other_hash) -> new_hash
+ * transform_keys(other_hash) {|old_key| ...} -> new_hash
+ * transform_keys -> new_enumerator
+ *
+ * With an argument, a block, or both given,
+ * derives a new hash +new_hash+ from +self+, the argument, and/or the block;
+ * all, some, or none of its keys may be different from those in +self+.
*
- * Returns a new \Hash object; each entry has:
- * * A key provided by the block.
- * * The value from +self+.
+ * With a block given and no argument,
+ * +new_hash+ has keys determined only by the block.
*
- * An optional hash argument can be provided to map keys to new keys.
- * Any key not given will be mapped using the provided block,
- * or remain the same if no block is given.
+ * For each key/value pair <tt>old_key/value</tt> in +self+, calls the block with +old_key+;
+ * the block's return value becomes +new_key+;
+ * sets <tt>new_hash[new_key] = value</tt>;
+ * a duplicate key overwrites:
*
- * Transform keys:
* h = {foo: 0, bar: 1, baz: 2}
- * h1 = h.transform_keys {|key| key.to_s }
- * h1 # => {"foo"=>0, "bar"=>1, "baz"=>2}
+ * h.transform_keys {|old_key| old_key.to_s }
+ * # => {"foo" => 0, "bar" => 1, "baz" => 2}
+ * h.transform_keys {|old_key| 'xxx' }
+ * # => {"xxx" => 2}
*
- * h.transform_keys(foo: :bar, bar: :foo)
- * #=> {bar: 0, foo: 1, baz: 2}
+ * With argument +other_hash+ given and no block,
+ * +new_hash+ may have new keys provided by +other_hash+
+ * and unchanged keys provided by +self+.
*
- * h.transform_keys(foo: :hello, &:to_s)
- * #=> {:hello=>0, "bar"=>1, "baz"=>2}
+ * For each key/value pair <tt>old_key/old_value</tt> in +self+,
+ * looks for key +old_key+ in +other_hash+:
*
- * Overwrites values for duplicate keys:
- * h = {foo: 0, bar: 1, baz: 2}
- * h1 = h.transform_keys {|key| :bat }
- * h1 # => {:bat=>2}
+ * - If +old_key+ is found, its value <tt>other_hash[old_key]</tt> is taken as +new_key+;
+ * sets <tt>new_hash[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys(baz: :BAZ, bar: :BAR, foo: :FOO)
+ * # => {FOO: 0, BAR: 1, BAZ: 2}
+ * h.transform_keys(baz: :FOO, bar: :FOO, foo: :FOO)
+ * # => {FOO: 2}
+ *
+ * - If +old_key+ is not found,
+ * sets <tt>new_hash[old_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys({})
+ * # => {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys(baz: :foo)
+ * # => {foo: 2, bar: 1}
+ *
+ * Unused keys in +other_hash+ are ignored:
*
- * Returns a new \Enumerator if no block given:
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.transform_keys # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:transform_keys>
- * h1 = e.each { |key| key.to_s }
- * h1 # => {"foo"=>0, "bar"=>1, "baz"=>2}
+ * h.transform_keys(bat: 3)
+ * # => {foo: 0, bar: 1, baz: 2}
+ *
+ * With both argument +other_hash+ and a block given,
+ * +new_hash+ has new keys specified by +other_hash+ or by the block,
+ * and unchanged keys provided by +self+.
+ *
+ * For each pair +old_key+ and +value+ in +self+:
+ *
+ * - If +other_hash+ has key +old_key+ (with value +new_key+),
+ * does not call the block for that key;
+ * sets <tt>new_hash[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys(baz: :BAZ, bar: :BAR, foo: :FOO) {|key| fail 'Not called' }
+ * # => {FOO: 0, BAR: 1, BAZ: 2}
+ *
+ * - If +other_hash+ does not have key +old_key+,
+ * calls the block with +old_key+ and takes its return value as +new_key+;
+ * sets <tt>new_hash[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys(baz: :BAZ) {|key| key.to_s.reverse }
+ * # => {"oof" => 0, "rab" => 1, BAZ: 2}
+ * h.transform_keys(baz: :BAZ) {|key| 'ook' }
+ * # => {"ook" => 1, BAZ: 2}
+ *
+ * With no argument and no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values].
*/
static VALUE
rb_hash_transform_keys(int argc, VALUE *argv, VALUE hash)
@@ -3289,13 +3298,97 @@ static int flatten_i(VALUE key, VALUE val, VALUE ary);
/*
* call-seq:
- * hash.transform_keys! {|key| ... } -> self
- * hash.transform_keys!(hash2) -> self
- * hash.transform_keys!(hash2) {|other_key| ...} -> self
- * hash.transform_keys! -> new_enumerator
+ * transform_keys! {|old_key| ... } -> self
+ * transform_keys!(other_hash) -> self
+ * transform_keys!(other_hash) {|old_key| ...} -> self
+ * transform_keys! -> new_enumerator
+ *
+ * With an argument, a block, or both given,
+ * derives keys from the argument, the block, and +self+;
+ * all, some, or none of the keys in +self+ may be changed.
+ *
+ * With a block given and no argument,
+ * derives keys only from the block;
+ * all, some, or none of the keys in +self+ may be changed.
+ *
+ * For each key/value pair <tt>old_key/value</tt> in +self+, calls the block with +old_key+;
+ * the block's return value becomes +new_key+;
+ * removes the entry for +old_key+: <tt>self.delete(old_key)</tt>;
+ * sets <tt>self[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys! {|old_key| old_key.to_s }
+ * # => {"foo" => 0, "bar" => 1, "baz" => 2}
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys! {|old_key| 'xxx' }
+ * # => {"xxx" => 2}
+ *
+ * With argument +other_hash+ given and no block,
+ * derives keys for +self+ from +other_hash+ and +self+;
+ * all, some, or none of the keys in +self+ may be changed.
+ *
+ * For each key/value pair <tt>old_key/old_value</tt> in +self+,
+ * looks for key +old_key+ in +other_hash+:
+ *
+ * - If +old_key+ is found, takes value <tt>other_hash[old_key]</tt> as +new_key+;
+ * removes the entry for +old_key+: <tt>self.delete(old_key)</tt>;
+ * sets <tt>self[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :BAZ, bar: :BAR, foo: :FOO)
+ * # => {FOO: 0, BAR: 1, BAZ: 2}
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :FOO, bar: :FOO, foo: :FOO)
+ * # => {FOO: 2}
+ *
+ * - If +old_key+ is not found, does nothing:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!({})
+ * # => {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :foo)
+ * # => {foo: 2, bar: 1}
+ *
+ * Unused keys in +other_hash+ are ignored:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(bat: 3)
+ * # => {foo: 0, bar: 1, baz: 2}
+ *
+ * With both argument +other_hash+ and a block given,
+ * derives keys from +other_hash+, the block, and +self+;
+ * all, some, or none of the keys in +self+ may be changed.
*
- * Same as Hash#transform_keys but modifies the receiver in place
- * instead of returning a new hash.
+ * For each pair +old_key+ and +value+ in +self+:
+ *
+ * - If +other_hash+ has key +old_key+ (with value +new_key+),
+ * does not call the block for that key;
+ * removes the entry for +old_key+: <tt>self.delete(old_key)</tt>;
+ * sets <tt>self[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :BAZ, bar: :BAR, foo: :FOO) {|key| fail 'Not called' }
+ * # => {FOO: 0, BAR: 1, BAZ: 2}
+ *
+ * - If +other_hash+ does not have key +old_key+,
+ * calls the block with +old_key+ and takes its return value as +new_key+;
+ * removes the entry for +old_key+: <tt>self.delete(old_key)</tt>;
+ * sets <tt>self[new_key] = value</tt>;
+ * a duplicate key overwrites:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :BAZ) {|key| key.to_s.reverse }
+ * # => {"oof" => 0, "rab" => 1, BAZ: 2}
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.transform_keys!(baz: :BAZ) {|key| 'ook' }
+ * # => {"ook" => 1, BAZ: 2}
+ *
+ * With no argument and no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values].
*/
static VALUE
rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash)
@@ -3342,6 +3435,7 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash)
rb_ary_clear(pairs);
rb_hash_clear(new_keys);
}
+ compact_after_delete(hash);
return hash;
}
@@ -3361,25 +3455,37 @@ transform_values_foreach_replace(st_data_t *key, st_data_t *value, st_data_t arg
return ST_CONTINUE;
}
+static VALUE
+transform_values_call(VALUE hash)
+{
+ rb_hash_stlike_foreach_with_replace(hash, transform_values_foreach_func, transform_values_foreach_replace, hash);
+ return hash;
+}
+
+static void
+transform_values(VALUE hash)
+{
+ hash_iter_lev_inc(hash);
+ rb_ensure(transform_values_call, hash, hash_foreach_ensure, hash);
+}
+
/*
* call-seq:
- * hash.transform_values {|value| ... } -> new_hash
- * hash.transform_values -> new_enumerator
+ * transform_values {|value| ... } -> new_hash
+ * transform_values -> new_enumerator
*
- * Returns a new \Hash object; each entry has:
- * * A key from +self+.
- * * A value provided by the block.
+ * With a block given, returns a new hash +new_hash+;
+ * for each pair +key+/+value+ in +self+,
+ * calls the block with +value+ and captures its return as +new_value+;
+ * adds to +new_hash+ the entry +key+/+new_value+:
*
- * Transform values:
* h = {foo: 0, bar: 1, baz: 2}
* h1 = h.transform_values {|value| value * 100}
- * h1 # => {:foo=>0, :bar=>100, :baz=>200}
+ * h1 # => {foo: 0, bar: 100, baz: 200}
*
- * Returns a new \Enumerator if no block given:
- * h = {foo: 0, bar: 1, baz: 2}
- * e = h.transform_values # => #<Enumerator: {:foo=>0, :bar=>1, :baz=>2}:transform_values>
- * h1 = e.each { |value| value * 100}
- * h1 # => {:foo=>0, :bar=>100, :baz=>200}
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values].
*/
static VALUE
rb_hash_transform_values(VALUE hash)
@@ -3391,7 +3497,8 @@ rb_hash_transform_values(VALUE hash)
SET_DEFAULT(result, Qnil);
if (!RHASH_EMPTY_P(hash)) {
- rb_hash_stlike_foreach_with_replace(result, transform_values_foreach_func, transform_values_foreach_replace, result);
+ transform_values(result);
+ compact_after_delete(result);
}
return result;
@@ -3399,18 +3506,24 @@ rb_hash_transform_values(VALUE hash)
/*
* call-seq:
- * hash.transform_values! {|value| ... } -> self
- * hash.transform_values! -> new_enumerator
+ * transform_values! {|old_value| ... } -> self
+ * transform_values! -> new_enumerator
*
- * Returns +self+, whose keys are unchanged, and whose values are determined by the given block.
- * h = {foo: 0, bar: 1, baz: 2}
- * h.transform_values! {|value| value * 100} # => {:foo=>0, :bar=>100, :baz=>200}
*
- * Returns a new \Enumerator if no block given:
+ * With a block given, changes the values of +self+ as determined by the block;
+ * returns +self+.
+ *
+ * For each entry +key+/+old_value+ in +self+,
+ * calls the block with +old_value+,
+ * captures its return value as +new_value+,
+ * and sets <tt>self[key] = new_value</tt>:
+ *
* h = {foo: 0, bar: 1, baz: 2}
- * e = h.transform_values! # => #<Enumerator: {:foo=>0, :bar=>100, :baz=>200}:transform_values!>
- * h1 = e.each {|value| value * 100}
- * h1 # => {:foo=>0, :bar=>100, :baz=>200}
+ * h.transform_values! {|value| value * 100} # => {foo: 0, bar: 100, baz: 200}
+ *
+ * With no block given, returns a new Enumerator.
+ *
+ * Related: see {Methods for Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values].
*/
static VALUE
rb_hash_transform_values_bang(VALUE hash)
@@ -3419,7 +3532,7 @@ rb_hash_transform_values_bang(VALUE hash)
rb_hash_modify_check(hash);
if (!RHASH_TABLE_EMPTY_P(hash)) {
- rb_hash_stlike_foreach_with_replace(hash, transform_values_foreach_func, transform_values_foreach_replace, hash);
+ transform_values(hash);
}
return hash;
@@ -3434,12 +3547,15 @@ to_a_i(VALUE key, VALUE value, VALUE ary)
/*
* call-seq:
- * hash.to_a -> new_array
+ * to_a -> new_array
+ *
+ * Returns all elements of +self+ as an array of 2-element arrays;
+ * each nested array contains a key-value pair from +self+:
*
- * Returns a new \Array of 2-element \Array objects;
- * each nested \Array contains a key-value pair from +self+:
* h = {foo: 0, bar: 1, baz: 2}
* h.to_a # => [[:foo, 0], [:bar, 1], [:baz, 2]]
+ *
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
@@ -3453,20 +3569,65 @@ rb_hash_to_a(VALUE hash)
return ary;
}
+static bool
+symbol_key_needs_quote(VALUE str)
+{
+ long len = RSTRING_LEN(str);
+ if (len == 0 || !rb_str_symname_p(str)) return true;
+ const char *s = RSTRING_PTR(str);
+ char first = s[0];
+ if (first == '@' || first == '$' || first == '!') return true;
+ if (!at_char_boundary(s, s + len - 1, RSTRING_END(str), rb_enc_get(str))) return false;
+ switch (s[len - 1]) {
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '`':
+ case '%':
+ case '^':
+ case '&':
+ case '|':
+ case ']':
+ case '<':
+ case '=':
+ case '>':
+ case '~':
+ case '@':
+ return true;
+ default:
+ return false;
+ }
+}
+
static int
inspect_i(VALUE key, VALUE value, VALUE str)
{
VALUE str2;
- str2 = rb_inspect(key);
+ bool is_symbol = SYMBOL_P(key);
+ bool quote = false;
+ if (is_symbol) {
+ str2 = rb_sym2str(key);
+ quote = symbol_key_needs_quote(str2);
+ }
+ else {
+ str2 = rb_inspect(key);
+ }
if (RSTRING_LEN(str) > 1) {
rb_str_buf_cat_ascii(str, ", ");
}
else {
rb_enc_copy(str, str2);
}
- rb_str_buf_append(str, str2);
- rb_str_buf_cat_ascii(str, "=>");
+ if (quote) {
+ rb_str_buf_append(str, rb_str_inspect(str2));
+ }
+ else {
+ rb_str_buf_append(str, str2);
+ }
+
+ rb_str_buf_cat_ascii(str, is_symbol ? ": " : " => ");
str2 = rb_inspect(value);
rb_str_buf_append(str, str2);
@@ -3488,13 +3649,14 @@ inspect_hash(VALUE hash, VALUE dummy, int recur)
/*
* call-seq:
- * hash.inspect -> new_string
+ * inspect -> new_string
+ *
+ * Returns a new string containing the hash entries:
*
- * Returns a new \String containing the hash entries:
* h = {foo: 0, bar: 1, baz: 2}
- * h.inspect # => "{:foo=>0, :bar=>1, :baz=>2}"
+ * h.inspect # => "{foo: 0, bar: 1, baz: 2}"
*
- * Hash#to_s is an alias for Hash#inspect.
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
@@ -3507,9 +3669,11 @@ rb_hash_inspect(VALUE hash)
/*
* call-seq:
- * hash.to_hash -> self
+ * to_hash -> self
*
* Returns +self+.
+ *
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
rb_hash_to_hash(VALUE hash)
@@ -3552,21 +3716,22 @@ rb_hash_to_h_block(VALUE hash)
/*
* call-seq:
- * hash.to_h -> self or new_hash
- * hash.to_h {|key, value| ... } -> new_hash
- *
- * For an instance of \Hash, returns +self+.
+ * to_h {|key, value| ... } -> new_hash
+ * to_h -> self or new_hash
*
- * For a subclass of \Hash, returns a new \Hash
- * containing the content of +self+.
+ * With a block given, returns a new hash whose content is based on the block;
+ * the block is called with each entry's key and value;
+ * the block should return a 2-element array
+ * containing the key and value to be included in the returned array:
*
- * When a block is given, returns a new \Hash object
- * whose content is based on the block;
- * the block should return a 2-element \Array object
- * specifying the key-value pair to be included in the returned \Array:
* h = {foo: 0, bar: 1, baz: 2}
- * h1 = h.to_h {|key, value| [value, key] }
- * h1 # => {0=>:foo, 1=>:bar, 2=>:baz}
+ * h.to_h {|key, value| [value, key] }
+ * # => {0 => :foo, 1 => :bar, 2 => :baz}
+ *
+ * With no block given, returns +self+ if +self+ is an instance of +Hash+;
+ * if +self+ is a subclass of +Hash+, returns a new hash containing the content of +self+.
+ *
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
@@ -3591,14 +3756,17 @@ keys_i(VALUE key, VALUE value, VALUE ary)
/*
* call-seq:
- * hash.keys -> new_array
+ * keys -> new_array
+ *
+ * Returns a new array containing all keys in +self+:
*
- * Returns a new \Array containing all keys in +self+:
* h = {foo: 0, bar: 1, baz: 2}
* h.keys # => [:foo, :bar, :baz]
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_hash_keys(VALUE hash)
{
st_index_t size = RHASH_SIZE(hash);
@@ -3607,7 +3775,7 @@ rb_hash_keys(VALUE hash)
if (size == 0) return keys;
if (ST_DATA_COMPATIBLE_P(VALUE)) {
- RARRAY_PTR_USE_TRANSIENT(keys, ptr, {
+ RARRAY_PTR_USE(keys, ptr, {
if (RHASH_AR_TABLE_P(hash)) {
size = ar_keys(hash, ptr, size);
}
@@ -3635,11 +3803,14 @@ values_i(VALUE key, VALUE value, VALUE ary)
/*
* call-seq:
- * hash.values -> new_array
+ * values -> new_array
+ *
+ * Returns a new array containing all values in +self+:
*
- * Returns a new \Array containing all values in +self+:
* h = {foo: 0, bar: 1, baz: 2}
* h.values # => [0, 1, 2]
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
VALUE
@@ -3654,20 +3825,19 @@ rb_hash_values(VALUE hash)
if (ST_DATA_COMPATIBLE_P(VALUE)) {
if (RHASH_AR_TABLE_P(hash)) {
rb_gc_writebarrier_remember(values);
- RARRAY_PTR_USE_TRANSIENT(values, ptr, {
+ RARRAY_PTR_USE(values, ptr, {
size = ar_values(hash, ptr, size);
});
}
else if (RHASH_ST_TABLE_P(hash)) {
st_table *table = RHASH_ST_TABLE(hash);
rb_gc_writebarrier_remember(values);
- RARRAY_PTR_USE_TRANSIENT(values, ptr, {
+ RARRAY_PTR_USE(values, ptr, {
size = st_values(table, ptr, size);
});
}
rb_ary_set_len(values, size);
}
-
else {
rb_hash_foreach(hash, values_i, values);
}
@@ -3677,17 +3847,18 @@ rb_hash_values(VALUE hash)
/*
* call-seq:
- * hash.include?(key) -> true or false
- * hash.has_key?(key) -> true or false
- * hash.key?(key) -> true or false
- * hash.member?(key) -> true or false
-
- * Methods #has_key?, #key?, and #member? are aliases for \#include?.
+ * include?(key) -> true or false
+ *
+ * Returns whether +key+ is a key in +self+:
*
- * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.include?(:bar) # => true
+ * h.include?(:BAR) # => false
+ *
+ * Related: {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_hash_has_key(VALUE hash, VALUE key)
{
return RBOOL(hash_stlike_lookup(hash, key, NULL));
@@ -3707,12 +3878,11 @@ rb_hash_search_value(VALUE key, VALUE value, VALUE arg)
/*
* call-seq:
- * hash.has_value?(value) -> true or false
- * hash.value?(value) -> true or false
+ * has_value?(value) -> true or false
*
- * Method #value? is an alias for \#has_value?.
+ * Returns whether +value+ is a value in +self+.
*
- * Returns +true+ if +value+ is a value in +self+, otherwise +false+.
+ * Related: {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
static VALUE
@@ -3809,21 +3979,25 @@ hash_equal(VALUE hash1, VALUE hash2, int eql)
/*
* call-seq:
- * hash == object -> true or false
+ * self == other -> true or false
*
- * Returns +true+ if all of the following are true:
- * * +object+ is a \Hash object.
- * * +hash+ and +object+ have the same keys (regardless of order).
- * * For each key +key+, <tt>hash[key] == object[key]</tt>.
+ * Returns whether all of the following are true:
*
- * Otherwise, returns +false+.
+ * - +other+ is a +Hash+ object (or can be converted to one).
+ * - +self+ and +other+ have the same keys (regardless of order).
+ * - For each key +key+, <tt>self[key] == other[key]</tt>.
*
- * Equal:
- * h1 = {foo: 0, bar: 1, baz: 2}
- * h2 = {foo: 0, bar: 1, baz: 2}
- * h1 == h2 # => true
- * h3 = {baz: 2, bar: 1, foo: 0}
- * h1 == h3 # => true
+ * Examples:
+ *
+ * h = {foo: 0, bar: 1}
+ * h == {foo: 0, bar: 1} # => true # Equal entries (same order)
+ * h == {bar: 1, foo: 0} # => true # Equal entries (different order).
+ * h == 1 # => false # Object not a hash.
+ * h == {} # => false # Different number of entries.
+ * h == {foo: 0, bar: 1} # => false # Different key.
+ * h == {foo: 0, bar: 1} # => false # Different value.
+ *
+ * Related: see {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
static VALUE
@@ -3834,21 +4008,23 @@ rb_hash_equal(VALUE hash1, VALUE hash2)
/*
* call-seq:
- * hash.eql? object -> true or false
+ * eql?(object) -> true or false
*
* Returns +true+ if all of the following are true:
- * * +object+ is a \Hash object.
- * * +hash+ and +object+ have the same keys (regardless of order).
- * * For each key +key+, <tt>h[key] eql? object[key]</tt>.
+ *
+ * - The given +object+ is a +Hash+ object.
+ * - +self+ and +object+ have the same keys (regardless of order).
+ * - For each key +key+, <tt>self[key].eql?(object[key])</tt>.
*
* Otherwise, returns +false+.
*
- * Equal:
* h1 = {foo: 0, bar: 1, baz: 2}
* h2 = {foo: 0, bar: 1, baz: 2}
* h1.eql? h2 # => true
* h3 = {baz: 2, bar: 1, foo: 0}
* h1.eql? h3 # => true
+ *
+ * Related: see {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
static VALUE
@@ -3871,16 +4047,19 @@ hash_i(VALUE key, VALUE val, VALUE arg)
/*
* call-seq:
- * hash.hash -> an_integer
+ * hash -> an_integer
*
- * Returns the \Integer hash-code for the hash.
+ * Returns the integer hash-code for the hash.
+ *
+ * Two hashes have the same hash-code if their content is the same
+ * (regardless of order):
*
- * Two \Hash objects have the same hash-code if their content is the same
- * (regardless or order):
* h1 = {foo: 0, bar: 1, baz: 2}
* h2 = {baz: 2, bar: 1, foo: 0}
* h2.hash == h1.hash # => true
* h2.eql? h1 # => true
+ *
+ * Related: see {Methods for Querying}[rdoc-ref:Hash@Methods+for+Querying].
*/
static VALUE
@@ -3905,17 +4084,21 @@ rb_hash_invert_i(VALUE key, VALUE value, VALUE hash)
/*
* call-seq:
- * hash.invert -> new_hash
+ * invert -> new_hash
+ *
+ * Returns a new hash with each key-value pair inverted:
*
- * Returns a new \Hash object with the each key-value pair inverted:
* h = {foo: 0, bar: 1, baz: 2}
* h1 = h.invert
* h1 # => {0=>:foo, 1=>:bar, 2=>:baz}
*
- * Overwrites any repeated new keys:
+ * Overwrites any repeated new keys
* (see {Entry Order}[rdoc-ref:Hash@Entry+Order]):
+ *
* h = {foo: 0, bar: 0, baz: 0}
* h.invert # => {0=>:baz}
+ *
+ * Related: see {Methods for Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values].
*/
static VALUE
@@ -3928,109 +4111,133 @@ rb_hash_invert(VALUE hash)
}
static int
-rb_hash_update_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
-{
- *value = arg->arg;
- return ST_CONTINUE;
-}
-
-NOINSERT_UPDATE_CALLBACK(rb_hash_update_callback)
-
-static int
rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
{
- RHASH_UPDATE(hash, key, rb_hash_update_callback, value);
+ rb_hash_aset(hash, key, value);
return ST_CONTINUE;
}
+struct update_call_args {
+ VALUE hash, newvalue, *argv;
+ int argc;
+ bool block_given;
+ bool iterating;
+};
+
static int
rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing)
{
- st_data_t newvalue = arg->arg;
+ VALUE k = (VALUE)*key, v = (VALUE)*value;
+ struct update_call_args *ua = (void *)arg->arg;
+ VALUE newvalue = ua->newvalue, hash = arg->hash;
if (existing) {
- newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue);
+ hash_iter_lev_inc(hash);
+ ua->iterating = true;
+ newvalue = rb_yield_values(3, k, v, newvalue);
+ hash_iter_lev_dec(hash);
+ ua->iterating = false;
}
- *value = newvalue;
+ else if (RHASH_STRING_KEY_P(hash, k) && !RB_OBJ_FROZEN(k)) {
+ *key = (st_data_t)rb_hash_key_str(k);
+ }
+ *value = (st_data_t)newvalue;
return ST_CONTINUE;
}
NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback)
static int
-rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
+rb_hash_update_block_i(VALUE key, VALUE value, VALUE args)
{
- RHASH_UPDATE(hash, key, rb_hash_update_block_callback, value);
+ struct update_call_args *ua = (void *)args;
+ ua->newvalue = value;
+ RHASH_UPDATE(ua->hash, key, rb_hash_update_block_callback, args);
return ST_CONTINUE;
}
+static VALUE
+rb_hash_update_call(VALUE args)
+{
+ struct update_call_args *arg = (void *)args;
+
+ for (int i = 0; i < arg->argc; i++){
+ VALUE hash = to_hash(arg->argv[i]);
+ if (arg->block_given) {
+ rb_hash_foreach(hash, rb_hash_update_block_i, args);
+ }
+ else {
+ rb_hash_foreach(hash, rb_hash_update_i, arg->hash);
+ }
+ }
+ return arg->hash;
+}
+
+static VALUE
+rb_hash_update_ensure(VALUE args)
+{
+ struct update_call_args *ua = (void *)args;
+ if (ua->iterating) hash_iter_lev_dec(ua->hash);
+ return Qnil;
+}
+
/*
* call-seq:
- * hash.merge! -> self
- * hash.merge!(*other_hashes) -> self
- * hash.merge!(*other_hashes) { |key, old_value, new_value| ... } -> self
+ * update(*other_hashes) -> self
+ * update(*other_hashes) { |key, old_value, new_value| ... } -> self
*
- * Merges each of +other_hashes+ into +self+; returns +self+.
+ * Updates values and/or adds entries to +self+; returns +self+.
*
- * Each argument in +other_hashes+ must be a \Hash.
+ * Each argument +other_hash+ in +other_hashes+ must be a hash.
*
- * \Method #update is an alias for \#merge!.
+ * With no block given, for each successive entry +key+/+new_value+ in each successive +other_hash+:
*
- * With arguments and no block:
- * * Returns +self+, after the given hashes are merged into it.
- * * The given hashes are merged left to right.
- * * Each new entry is added at the end.
- * * Each duplicate-key entry's value overwrites the previous value.
+ * - If +key+ is in +self+, sets <tt>self[key] = new_value</tt>, whose position is unchanged:
*
- * Example:
- * h = {foo: 0, bar: 1, baz: 2}
- * h1 = {bat: 3, bar: 4}
- * h2 = {bam: 5, bat:6}
- * h.merge!(h1, h2) # => {:foo=>0, :bar=>4, :baz=>2, :bat=>6, :bam=>5}
+ * h0 = {foo: 0, bar: 1, baz: 2}
+ * h1 = {bar: 3, foo: -1}
+ * h0.update(h1) # => {foo: -1, bar: 3, baz: 2}
*
- * With arguments and a block:
- * * Returns +self+, after the given hashes are merged.
- * * The given hashes are merged left to right.
- * * Each new-key entry is added at the end.
- * * For each duplicate key:
- * * Calls the block with the key and the old and new values.
- * * The block's return value becomes the new value for the entry.
+ * - If +key+ is not in +self+, adds the entry at the end of +self+:
*
- * Example:
- * h = {foo: 0, bar: 1, baz: 2}
- * h1 = {bat: 3, bar: 4}
- * h2 = {bam: 5, bat:6}
- * h3 = h.merge!(h1, h2) { |key, old_value, new_value| old_value + new_value }
- * h3 # => {:foo=>0, :bar=>5, :baz=>2, :bat=>9, :bam=>5}
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.update({bam: 3, bah: 4}) # => {foo: 0, bar: 1, baz: 2, bam: 3, bah: 4}
*
- * With no arguments:
- * * Returns +self+, unmodified.
- * * The block, if given, is ignored.
+ * With a block given, for each successive entry +key+/+new_value+ in each successive +other_hash+:
*
- * Example:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.merge # => {:foo=>0, :bar=>1, :baz=>2}
- * h1 = h.merge! { |key, old_value, new_value| raise 'Cannot happen' }
- * h1 # => {:foo=>0, :bar=>1, :baz=>2}
+ * - If +key+ is in +self+, fetches +old_value+ from <tt>self[key]</tt>,
+ * calls the block with +key+, +old_value+, and +new_value+,
+ * and sets <tt>self[key] = new_value</tt>, whose position is unchanged :
+ *
+ * season = {AB: 75, H: 20, HR: 3, SO: 17, W: 11, HBP: 3}
+ * today = {AB: 3, H: 1, W: 1}
+ * yesterday = {AB: 4, H: 2, HR: 1}
+ * season.update(yesterday, today) {|key, old_value, new_value| old_value + new_value }
+ * # => {AB: 82, H: 23, HR: 4, SO: 17, W: 12, HBP: 3}
+ *
+ * - If +key+ is not in +self+, adds the entry at the end of +self+:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h.update({bat: 3}) { fail 'Cannot happen' }
+ * # => {foo: 0, bar: 1, baz: 2, bat: 3}
+ *
+ * Related: see {Methods for Assigning}[rdoc-ref:Hash@Methods+for+Assigning].
*/
static VALUE
rb_hash_update(int argc, VALUE *argv, VALUE self)
{
- int i;
- bool block_given = rb_block_given_p();
+ struct update_call_args args = {
+ .hash = self,
+ .argv = argv,
+ .argc = argc,
+ .block_given = rb_block_given_p(),
+ .iterating = false,
+ };
+ VALUE arg = (VALUE)&args;
rb_hash_modify(self);
- for (i = 0; i < argc; i++){
- VALUE hash = to_hash(argv[i]);
- if (block_given) {
- rb_hash_foreach(hash, rb_hash_update_block_i, self);
- }
- else {
- rb_hash_foreach(hash, rb_hash_update_i, self);
- }
- }
- return self;
+ return rb_ensure(rb_hash_update_call, arg, rb_hash_update_ensure, arg);
}
struct update_func_arg {
@@ -4084,53 +4291,48 @@ rb_hash_update_by(VALUE hash1, VALUE hash2, rb_hash_update_func *func)
/*
* call-seq:
- * hash.merge -> copy_of_self
- * hash.merge(*other_hashes) -> new_hash
- * hash.merge(*other_hashes) { |key, old_value, new_value| ... } -> new_hash
- *
- * Returns the new \Hash formed by merging each of +other_hashes+
- * into a copy of +self+.
+ * merge(*other_hashes) -> new_hash
+ * merge(*other_hashes) { |key, old_value, new_value| ... } -> new_hash
*
- * Each argument in +other_hashes+ must be a \Hash.
+ * Each argument +other_hash+ in +other_hashes+ must be a hash.
*
- * ---
+ * With arguments +other_hashes+ given and no block,
+ * returns the new hash formed by merging each successive +other_hash+
+ * into a copy of +self+;
+ * returns that copy;
+ * for each successive entry in +other_hash+:
*
- * With arguments and no block:
- * * Returns the new \Hash object formed by merging each successive
- * \Hash in +other_hashes+ into +self+.
- * * Each new-key entry is added at the end.
- * * Each duplicate-key entry's value overwrites the previous value.
+ * - For a new key, the entry is added at the end of +self+.
+ * - For duplicate key, the entry overwrites the entry in +self+,
+ * whose position is unchanged.
*
* Example:
+ *
* h = {foo: 0, bar: 1, baz: 2}
* h1 = {bat: 3, bar: 4}
* h2 = {bam: 5, bat:6}
- * h.merge(h1, h2) # => {:foo=>0, :bar=>4, :baz=>2, :bat=>6, :bam=>5}
+ * h.merge(h1, h2) # => {foo: 0, bar: 4, baz: 2, bat: 6, bam: 5}
+ *
+ * With arguments +other_hashes+ and a block given, behaves as above
+ * except that for a duplicate key
+ * the overwriting entry takes it value not from the entry in +other_hash+,
+ * but instead from the block:
*
- * With arguments and a block:
- * * Returns a new \Hash object that is the merge of +self+ and each given hash.
- * * The given hashes are merged left to right.
- * * Each new-key entry is added at the end.
- * * For each duplicate key:
- * * Calls the block with the key and the old and new values.
- * * The block's return value becomes the new value for the entry.
+ * - The block is called with the duplicate key and the values
+ * from both +self+ and +other_hash+.
+ * - The block's return value becomes the new value for the entry in +self+.
*
* Example:
+ *
* h = {foo: 0, bar: 1, baz: 2}
* h1 = {bat: 3, bar: 4}
* h2 = {bam: 5, bat:6}
- * h3 = h.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
- * h3 # => {:foo=>0, :bar=>5, :baz=>2, :bat=>9, :bam=>5}
+ * h.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
+ * # => {foo: 0, bar: 5, baz: 2, bat: 9, bam: 5}
*
- * With no arguments:
- * * Returns a copy of +self+.
- * * The block, if given, is ignored.
+ * With no arguments, returns a copy of +self+; the block, if given, is ignored.
*
- * Example:
- * h = {foo: 0, bar: 1, baz: 2}
- * h.merge # => {:foo=>0, :bar=>1, :baz=>2}
- * h1 = h.merge { |key, old_value, new_value| raise 'Cannot happen' }
- * h1 # => {:foo=>0, :bar=>1, :baz=>2}
+ * Related: see {Methods for Assigning}[rdoc-ref:Hash@Methods+for+Assigning].
*/
static VALUE
@@ -4145,24 +4347,17 @@ assoc_cmp(VALUE a, VALUE b)
return !RTEST(rb_equal(a, b));
}
-static VALUE
-lookup2_call(VALUE arg)
-{
- VALUE *args = (VALUE *)arg;
- return rb_hash_lookup2(args[0], args[1], Qundef);
-}
-
-struct reset_hash_type_arg {
- VALUE hash;
- const struct st_hash_type *orighash;
+struct assoc_arg {
+ st_table *tbl;
+ st_data_t key;
};
static VALUE
-reset_hash_type(VALUE arg)
+assoc_lookup(VALUE arg)
{
- struct reset_hash_type_arg *p = (struct reset_hash_type_arg *)arg;
- HASH_ASSERT(RHASH_ST_TABLE_P(p->hash));
- RHASH_ST_TABLE(p->hash)->type = p->orighash;
+ struct assoc_arg *p = (struct assoc_arg*)arg;
+ st_data_t data;
+ if (st_lookup(p->tbl, p->key, &data)) return (VALUE)data;
return Qundef;
}
@@ -4180,42 +4375,46 @@ assoc_i(VALUE key, VALUE val, VALUE arg)
/*
* call-seq:
- * hash.assoc(key) -> new_array or nil
+ * assoc(key) -> entry or nil
+ *
+ * If the given +key+ is found, returns its entry as a 2-element array
+ * containing that key and its value:
*
- * If the given +key+ is found, returns a 2-element \Array containing that key and its value:
* h = {foo: 0, bar: 1, baz: 2}
* h.assoc(:bar) # => [:bar, 1]
*
- * Returns +nil+ if key +key+ is not found.
+ * Returns +nil+ if the key is not found.
+ *
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
rb_hash_assoc(VALUE hash, VALUE key)
{
- st_table *table;
- const struct st_hash_type *orighash;
VALUE args[2];
if (RHASH_EMPTY_P(hash)) return Qnil;
- ar_force_convert_table(hash, __FILE__, __LINE__);
- HASH_ASSERT(RHASH_ST_TABLE_P(hash));
- table = RHASH_ST_TABLE(hash);
- orighash = table->type;
-
- if (orighash != &identhash) {
- VALUE value;
- struct reset_hash_type_arg ensure_arg;
- struct st_hash_type assochash;
-
- assochash.compare = assoc_cmp;
- assochash.hash = orighash->hash;
- table->type = &assochash;
- args[0] = hash;
- args[1] = key;
- ensure_arg.hash = hash;
- ensure_arg.orighash = orighash;
- value = rb_ensure(lookup2_call, (VALUE)&args, reset_hash_type, (VALUE)&ensure_arg);
+ if (RHASH_ST_TABLE_P(hash) && !RHASH_IDENTHASH_P(hash)) {
+ VALUE value = Qundef;
+ st_table assoctable = *RHASH_ST_TABLE(hash);
+ assoctable.type = &(struct st_hash_type){
+ .compare = assoc_cmp,
+ .hash = assoctable.type->hash,
+ };
+ VALUE arg = (VALUE)&(struct assoc_arg){
+ .tbl = &assoctable,
+ .key = (st_data_t)key,
+ };
+
+ if (RB_OBJ_FROZEN(hash)) {
+ value = assoc_lookup(arg);
+ }
+ else {
+ hash_iter_lev_inc(hash);
+ value = rb_ensure(assoc_lookup, arg, hash_foreach_ensure, hash);
+ }
+ hash_verify(hash);
if (!UNDEF_P(value)) return rb_assoc_new(key, value);
}
@@ -4239,15 +4438,18 @@ rassoc_i(VALUE key, VALUE val, VALUE arg)
/*
* call-seq:
- * hash.rassoc(value) -> new_array or nil
+ * rassoc(value) -> new_array or nil
+ *
+ * Searches +self+ for the first entry whose value is <tt>==</tt> to the given +value+;
+ * see {Entry Order}[rdoc-ref:Hash@Entry+Order].
+ *
+ * If the entry is found, returns its key and value as a 2-element array;
+ * returns +nil+ if not found:
*
- * Returns a new 2-element \Array consisting of the key and value
- * of the first-found entry whose value is <tt>==</tt> to value
- * (see {Entry Order}[rdoc-ref:Hash@Entry+Order]):
* h = {foo: 0, bar: 1, baz: 1}
* h.rassoc(1) # => [:bar, 1]
*
- * Returns +nil+ if no such value found.
+ * Related: see {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -4275,33 +4477,38 @@ flatten_i(VALUE key, VALUE val, VALUE ary)
/*
* call-seq:
- * hash.flatten -> new_array
- * hash.flatten(level) -> new_array
+ * flatten(depth = 1) -> new_array
+ *
+ * With positive integer +depth+,
+ * returns a new array that is a recursive flattening of +self+ to the given +depth+.
+ *
+ * At each level of recursion:
*
- * Returns a new \Array object that is a 1-dimensional flattening of +self+.
+ * - Each element whose value is an array is "flattened" (that is, replaced by its individual array elements);
+ * see Array#flatten.
+ * - Each element whose value is not an array is unchanged.
+ * even if the value is an object that has instance method flatten (such as a hash).
*
- * ---
+ * Examples; note that entry <tt>foo: {bar: 1, baz: 2}</tt> is never flattened.
*
- * By default, nested Arrays are not flattened:
- * h = {foo: 0, bar: [:bat, 3], baz: 2}
- * h.flatten # => [:foo, 0, :bar, [:bat, 3], :baz, 2]
+ * h = {foo: {bar: 1, baz: 2}, bat: [:bam, [:bap, [:bah]]]}
+ * h.flatten(1) # => [:foo, {:bar=>1, :baz=>2}, :bat, [:bam, [:bap, [:bah]]]]
+ * h.flatten(2) # => [:foo, {:bar=>1, :baz=>2}, :bat, :bam, [:bap, [:bah]]]
+ * h.flatten(3) # => [:foo, {:bar=>1, :baz=>2}, :bat, :bam, :bap, [:bah]]
+ * h.flatten(4) # => [:foo, {:bar=>1, :baz=>2}, :bat, :bam, :bap, :bah]
+ * h.flatten(5) # => [:foo, {:bar=>1, :baz=>2}, :bat, :bam, :bap, :bah]
*
- * Takes the depth of recursive flattening from \Integer argument +level+:
- * h = {foo: 0, bar: [:bat, [:baz, [:bat, ]]]}
- * h.flatten(1) # => [:foo, 0, :bar, [:bat, [:baz, [:bat]]]]
- * h.flatten(2) # => [:foo, 0, :bar, :bat, [:baz, [:bat]]]
- * h.flatten(3) # => [:foo, 0, :bar, :bat, :baz, [:bat]]
- * h.flatten(4) # => [:foo, 0, :bar, :bat, :baz, :bat]
+ * With negative integer +depth+,
+ * flattens all levels:
*
- * When +level+ is negative, flattens all nested Arrays:
- * h = {foo: 0, bar: [:bat, [:baz, [:bat, ]]]}
- * h.flatten(-1) # => [:foo, 0, :bar, :bat, :baz, :bat]
- * h.flatten(-2) # => [:foo, 0, :bar, :bat, :baz, :bat]
+ * h.flatten(-1) # => [:foo, {:bar=>1, :baz=>2}, :bat, :bam, :bap, :bah]
*
- * When +level+ is zero, returns the equivalent of #to_a :
- * h = {foo: 0, bar: [:bat, 3], baz: 2}
- * h.flatten(0) # => [[:foo, 0], [:bar, [:bat, 3]], [:baz, 2]]
- * h.flatten(0) == h.to_a # => true
+ * With +depth+ zero,
+ * returns the equivalent of #to_a:
+ *
+ * h.flatten(0) # => [[:foo, {:bar=>1, :baz=>2}], [:bat, [:bam, [:bap, [:bah]]]]]
+ *
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
@@ -4346,44 +4553,46 @@ delete_if_nil(VALUE key, VALUE value, VALUE hash)
return ST_CONTINUE;
}
-static int
-set_if_not_nil(VALUE key, VALUE value, VALUE hash)
-{
- if (!NIL_P(value)) {
- rb_hash_aset(hash, key, value);
- }
- return ST_CONTINUE;
-}
-
/*
* call-seq:
- * hash.compact -> new_hash
+ * compact -> new_hash
*
* Returns a copy of +self+ with all +nil+-valued entries removed:
+ *
* h = {foo: 0, bar: nil, baz: 2, bat: nil}
- * h1 = h.compact
- * h1 # => {:foo=>0, :baz=>2}
+ * h.compact # => {foo: 0, baz: 2}
+ *
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
rb_hash_compact(VALUE hash)
{
- VALUE result = rb_hash_new();
+ VALUE result = rb_hash_dup(hash);
if (!RHASH_EMPTY_P(hash)) {
- rb_hash_foreach(hash, set_if_not_nil, result);
+ rb_hash_foreach(result, delete_if_nil, result);
+ compact_after_delete(result);
+ }
+ else if (rb_hash_compare_by_id_p(hash)) {
+ result = rb_hash_compare_by_id(result);
}
return result;
}
/*
* call-seq:
- * hash.compact! -> self or nil
+ * compact! -> self or nil
+ *
+ * If +self+ contains any +nil+-valued entries,
+ * returns +self+ with all +nil+-valued entries removed;
+ * returns +nil+ otherwise:
*
- * Returns +self+ with all its +nil+-valued entries removed (in place):
* h = {foo: 0, bar: nil, baz: 2, bat: nil}
- * h.compact! # => {:foo=>0, :baz=>2}
+ * h.compact!
+ * h # => {foo: 0, baz: 2}
+ * h.compact! # => nil
*
- * Returns +nil+ if no entries were removed.
+ * Related: see {Methods for Deleting}[rdoc-ref:Hash@Methods+for+Deleting].
*/
static VALUE
@@ -4400,34 +4609,30 @@ rb_hash_compact_bang(VALUE hash)
return Qnil;
}
-static st_table *rb_init_identtable_with_size(st_index_t size);
-
/*
* call-seq:
- * hash.compare_by_identity -> self
+ * compare_by_identity -> self
*
- * Sets +self+ to consider only identity in comparing keys;
- * two keys are considered the same only if they are the same object;
- * returns +self+.
+ * Sets +self+ to compare keys using _identity_ (rather than mere _equality_);
+ * returns +self+:
+ *
+ * By default, two keys are considered to be the same key
+ * if and only if they are _equal_ objects (per method #eql?):
*
- * By default, these two object are considered to be the same key,
- * so +s1+ will overwrite +s0+:
- * s0 = 'x'
- * s1 = 'x'
* h = {}
- * h.compare_by_identity? # => false
- * h[s0] = 0
- * h[s1] = 1
+ * h['x'] = 0
+ * h['x'] = 1 # Overwrites.
* h # => {"x"=>1}
*
- * After calling \#compare_by_identity, the keys are considered to be different,
- * and therefore do not overwrite each other:
- * h = {}
- * h.compare_by_identity # => {}
- * h.compare_by_identity? # => true
- * h[s0] = 0
- * h[s1] = 1
- * h # => {"x"=>0, "x"=>1}
+ * When this method has been called, two keys are considered to be the same key
+ * if and only if they are the _same_ object:
+ *
+ * h.compare_by_identity
+ * h['x'] = 2 # Does not overwrite.
+ * h # => {"x"=>1, "x"=>2}
+ *
+ * Related: #compare_by_identity?;
+ * see also {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
VALUE
@@ -4439,38 +4644,66 @@ rb_hash_compare_by_id(VALUE hash)
if (rb_hash_compare_by_id_p(hash)) return hash;
rb_hash_modify_check(hash);
- ar_force_convert_table(hash, __FILE__, __LINE__);
- HASH_ASSERT(RHASH_ST_TABLE_P(hash));
+ if (hash_iterating_p(hash)) {
+ rb_raise(rb_eRuntimeError, "compare_by_identity during iteration");
+ }
+
+ if (RHASH_TABLE_EMPTY_P(hash)) {
+ // Fast path: There's nothing to rehash, so we don't need a `tmp` table.
+ // We're most likely an AR table, so this will need an allocation.
+ ar_force_convert_table(hash, __FILE__, __LINE__);
+ HASH_ASSERT(RHASH_ST_TABLE_P(hash));
+
+ RHASH_ST_TABLE(hash)->type = &identhash;
+ }
+ else {
+ // Slow path: Need to rehash the members of `self` into a new
+ // `tmp` table using the new `identhash` compare/hash functions.
+ tmp = hash_alloc(0);
+ hash_st_table_init(tmp, &identhash, RHASH_SIZE(hash));
+ identtable = RHASH_ST_TABLE(tmp);
+
+ rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp);
+ rb_hash_free(hash);
+
+ // We know for sure `identtable` is an st table,
+ // so we can skip `ar_force_convert_table` here.
+ RHASH_ST_TABLE_SET(hash, identtable);
+ RHASH_ST_CLEAR(tmp);
+ }
- tmp = hash_alloc(0);
- identtable = rb_init_identtable_with_size(RHASH_SIZE(hash));
- RHASH_ST_TABLE_SET(tmp, identtable);
- rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tmp);
- st_free_table(RHASH_ST_TABLE(hash));
- RHASH_ST_TABLE_SET(hash, identtable);
- RHASH_ST_CLEAR(tmp);
+ rb_gc_register_pinning_obj(hash);
return hash;
}
/*
* call-seq:
- * hash.compare_by_identity? -> true or false
+ * compare_by_identity? -> true or false
*
- * Returns +true+ if #compare_by_identity has been called, +false+ otherwise.
+ * Returns whether #compare_by_identity has been called:
+ *
+ * h = {}
+ * h.compare_by_identity? # => false
+ * h.compare_by_identity
+ * h.compare_by_identity? # => true
+ *
+ * Related: #compare_by_identity;
+ * see also {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
-MJIT_FUNC_EXPORTED VALUE
+VALUE
rb_hash_compare_by_id_p(VALUE hash)
{
- return RBOOL(RHASH_ST_TABLE_P(hash) && RHASH_ST_TABLE(hash)->type == &identhash);
+ return RBOOL(RHASH_IDENTHASH_P(hash));
}
VALUE
rb_ident_hash_new(void)
{
VALUE hash = rb_hash_new();
- RHASH_ST_TABLE_SET(hash, st_init_table(&identhash));
+ hash_st_table_init(hash, &identhash, 0);
+ rb_gc_register_pinning_obj(hash);
return hash;
}
@@ -4478,7 +4711,8 @@ VALUE
rb_ident_hash_new_with_size(st_index_t size)
{
VALUE hash = rb_hash_new();
- RHASH_ST_TABLE_SET(hash, st_init_table_with_size(&identhash, size));
+ hash_st_table_init(hash, &identhash, size);
+ rb_gc_register_pinning_obj(hash);
return hash;
}
@@ -4488,12 +4722,6 @@ rb_init_identtable(void)
return st_init_table(&identhash);
}
-static st_table *
-rb_init_identtable_with_size(st_index_t size)
-{
- return st_init_table_with_size(&identhash, size);
-}
-
static int
any_p_i(VALUE key, VALUE value, VALUE arg)
{
@@ -4529,31 +4757,42 @@ any_p_i_pattern(VALUE key, VALUE value, VALUE arg)
/*
* call-seq:
- * hash.any? -> true or false
- * hash.any?(object) -> true or false
- * hash.any? {|key, value| ... } -> true or false
+ * any? -> true or false
+ * any?(entry) -> true or false
+ * any? {|key, value| ... } -> true or false
*
* Returns +true+ if any element satisfies a given criterion;
* +false+ otherwise.
*
+ * If +self+ has no element, returns +false+ and argument or block are not used;
+ * otherwise behaves as below.
+ *
* With no argument and no block,
- * returns +true+ if +self+ is non-empty; +false+ if empty.
+ * returns +true+ if +self+ is non-empty, +false+ otherwise.
*
- * With argument +object+ and no block,
+ * With argument +entry+ and no block,
* returns +true+ if for any key +key+
- * <tt>h.assoc(key) == object</tt>:
+ * <tt>self.assoc(key) == entry</tt>, +false+ otherwise:
+ *
* h = {foo: 0, bar: 1, baz: 2}
+ * h.assoc(:bar) # => [:bar, 1]
* h.any?([:bar, 1]) # => true
* h.any?([:bar, 0]) # => false
- * h.any?([:baz, 1]) # => false
*
- * With no argument and a block,
+ * With no argument and a block given,
* calls the block with each key-value pair;
- * returns +true+ if the block returns any truthy value,
+ * returns +true+ if the block returns a truthy value,
* +false+ otherwise:
+ *
* h = {foo: 0, bar: 1, baz: 2}
* h.any? {|key, value| value < 3 } # => true
* h.any? {|key, value| value > 3 } # => false
+ *
+ * With both argument +entry+ and a block given,
+ * issues a warning and ignores the block.
+ *
+ * Related: Enumerable#any? (which this method overrides);
+ * see also {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -4587,31 +4826,37 @@ rb_hash_any_p(int argc, VALUE *argv, VALUE hash)
/*
* call-seq:
- * hash.dig(key, *identifiers) -> object
+ * dig(key, *identifiers) -> object
+ *
+ * Finds and returns an object found in nested objects,
+ * as specified by +key+ and +identifiers+.
*
- * Finds and returns the object in nested objects
- * that is specified by +key+ and +identifiers+.
* The nested objects may be instances of various classes.
* See {Dig Methods}[rdoc-ref:dig_methods.rdoc].
*
- * Nested Hashes:
+ * Nested hashes:
+ *
* h = {foo: {bar: {baz: 2}}}
- * h.dig(:foo) # => {:bar=>{:baz=>2}}
- * h.dig(:foo, :bar) # => {:baz=>2}
+ * h.dig(:foo) # => {bar: {baz: 2}}
+ * h.dig(:foo, :bar) # => {baz: 2}
* h.dig(:foo, :bar, :baz) # => 2
* h.dig(:foo, :bar, :BAZ) # => nil
*
- * Nested Hashes and Arrays:
+ * Nested hashes and arrays:
+ *
* h = {foo: {bar: [:a, :b, :c]}}
* h.dig(:foo, :bar, 2) # => :c
*
- * This method will use the {default values}[rdoc-ref:Hash@Default+Values]
- * for keys that are not present:
+ * If no such object is found,
+ * returns the {hash default}[rdoc-ref:Hash@Hash+Default]:
+ *
* h = {foo: {bar: [:a, :b, :c]}}
* h.dig(:hello) # => nil
* h.default_proc = -> (hash, _key) { hash }
- * h.dig(:hello, :world) # => h
- * h.dig(:hello, :world, :foo, :bar, 2) # => :c
+ * h.dig(:hello, :world)
+ * # => {:foo=>{:bar=>[:a, :b, :c]}}
+ *
+ * Related: {Methods for Fetching}[rdoc-ref:Hash@Methods+for+Fetching].
*/
static VALUE
@@ -4646,14 +4891,21 @@ hash_le(VALUE hash1, VALUE hash2)
/*
* call-seq:
- * hash <= other_hash -> true or false
+ * self <= other -> true or false
*
- * Returns +true+ if +hash+ is a subset of +other_hash+, +false+ otherwise:
- * h1 = {foo: 0, bar: 1}
- * h2 = {foo: 0, bar: 1, baz: 2}
- * h1 <= h2 # => true
- * h2 <= h1 # => false
- * h1 <= h1 # => true
+ * Returns whether the entries of +self+ are a subset of the entries of +other+:
+ *
+ * h0 = {foo: 0, bar: 1}
+ * h1 = {foo: 0, bar: 1, baz: 2}
+ * h0 <= h0 # => true
+ * h0 <= h1 # => true
+ * h1 <= h0 # => false
+ *
+ * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc].
+ *
+ * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash.
+ *
+ * Related: see {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
static VALUE
rb_hash_le(VALUE hash, VALUE other)
@@ -4665,14 +4917,23 @@ rb_hash_le(VALUE hash, VALUE other)
/*
* call-seq:
- * hash < other_hash -> true or false
+ * self < other -> true or false
*
- * Returns +true+ if +hash+ is a proper subset of +other_hash+, +false+ otherwise:
- * h1 = {foo: 0, bar: 1}
- * h2 = {foo: 0, bar: 1, baz: 2}
- * h1 < h2 # => true
- * h2 < h1 # => false
- * h1 < h1 # => false
+ * Returns whether the entries of +self+ are a proper subset of the entries of +other+:
+ *
+ * h = {foo: 0, bar: 1}
+ * h < {foo: 0, bar: 1, baz: 2} # => true # Proper subset.
+ * h < {baz: 2, bar: 1, foo: 0} # => true # Order may differ.
+ * h < h # => false # Not a proper subset.
+ * h < {bar: 1, foo: 0} # => false # Not a proper subset.
+ * h < {foo: 0, bar: 1, baz: 2} # => false # Different key.
+ * h < {foo: 0, bar: 1, baz: 2} # => false # Different value.
+ *
+ * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc].
+ *
+ * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash.
+ *
+ * Related: see {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
static VALUE
rb_hash_lt(VALUE hash, VALUE other)
@@ -4684,14 +4945,21 @@ rb_hash_lt(VALUE hash, VALUE other)
/*
* call-seq:
- * hash >= other_hash -> true or false
+ * self >= other -> true or false
*
- * Returns +true+ if +hash+ is a superset of +other_hash+, +false+ otherwise:
- * h1 = {foo: 0, bar: 1, baz: 2}
- * h2 = {foo: 0, bar: 1}
- * h1 >= h2 # => true
- * h2 >= h1 # => false
- * h1 >= h1 # => true
+ * Returns whether the entries of +self+ are a superset of the entries of +other+:
+ *
+ * h0 = {foo: 0, bar: 1, baz: 2}
+ * h1 = {foo: 0, bar: 1}
+ * h0 >= h1 # => true
+ * h0 >= h0 # => true
+ * h1 >= h0 # => false
+ *
+ * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc].
+ *
+ * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash.
+ *
+ * Related: see {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
static VALUE
rb_hash_ge(VALUE hash, VALUE other)
@@ -4703,14 +4971,23 @@ rb_hash_ge(VALUE hash, VALUE other)
/*
* call-seq:
- * hash > other_hash -> true or false
+ * self > other -> true or false
*
- * Returns +true+ if +hash+ is a proper superset of +other_hash+, +false+ otherwise:
- * h1 = {foo: 0, bar: 1, baz: 2}
- * h2 = {foo: 0, bar: 1}
- * h1 > h2 # => true
- * h2 > h1 # => false
- * h1 > h1 # => false
+ * Returns whether the entries of +self+ are a proper superset of the entries of +other+:
+ *
+ * h = {foo: 0, bar: 1, baz: 2}
+ * h > {foo: 0, bar: 1} # => true # Proper superset.
+ * h > {bar: 1, foo: 0} # => true # Order may differ.
+ * h > h # => false # Not a proper superset.
+ * h > {baz: 2, bar: 1, foo: 0} # => false # Not a proper superset.
+ * h > {foo: 0, bar: 1} # => false # Different key.
+ * h > {foo: 0, bar: 1} # => false # Different value.
+ *
+ * See {Hash Inclusion}[rdoc-ref:language/hash_inclusion.rdoc].
+ *
+ * Raises TypeError if +other_hash+ is not a hash and cannot be converted to a hash.
+ *
+ * Related: see {Methods for Comparing}[rdoc-ref:Hash@Methods+for+Comparing].
*/
static VALUE
rb_hash_gt(VALUE hash, VALUE other)
@@ -4729,15 +5006,18 @@ hash_proc_call(RB_BLOCK_CALL_FUNC_ARGLIST(key, hash))
/*
* call-seq:
- * hash.to_proc -> proc
+ * to_proc -> proc
+ *
+ * Returns a Proc object that maps a key to its value:
*
- * Returns a \Proc object that maps a key to its value:
* h = {foo: 0, bar: 1, baz: 2}
* proc = h.to_proc
* proc.class # => Proc
* proc.call(:foo) # => 0
* proc.call(:bar) # => 1
* proc.call(:nosuch) # => nil
+ *
+ * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting].
*/
static VALUE
rb_hash_to_proc(VALUE hash)
@@ -4745,6 +5025,7 @@ rb_hash_to_proc(VALUE hash)
return rb_func_lambda_new(hash_proc_call, hash, 1, 1);
}
+/* :nodoc: */
static VALUE
rb_hash_deconstruct_keys(VALUE hash, VALUE keys)
{
@@ -4754,10 +5035,8 @@ rb_hash_deconstruct_keys(VALUE hash, VALUE keys)
static int
add_new_i(st_data_t *key, st_data_t *val, st_data_t arg, int existing)
{
- VALUE *args = (VALUE *)arg;
if (existing) return ST_STOP;
- RB_OBJ_WRITTEN(args[0], Qundef, (VALUE)*key);
- RB_OBJ_WRITE(args[0], (VALUE *)val, args[1]);
+ *val = arg;
return ST_CONTINUE;
}
@@ -4769,23 +5048,25 @@ int
rb_hash_add_new_element(VALUE hash, VALUE key, VALUE val)
{
st_table *tbl;
- int ret = 0;
- VALUE args[2];
- args[0] = hash;
- args[1] = val;
+ int ret = -1;
if (RHASH_AR_TABLE_P(hash)) {
- hash_ar_table(hash);
-
- ret = ar_update(hash, (st_data_t)key, add_new_i, (st_data_t)args);
- if (ret != -1) {
- return ret;
+ ret = ar_update(hash, (st_data_t)key, add_new_i, (st_data_t)val);
+ if (ret == -1) {
+ ar_force_convert_table(hash, __FILE__, __LINE__);
}
- ar_try_convert_table(hash);
}
- tbl = RHASH_TBL_RAW(hash);
- return st_update(tbl, (st_data_t)key, add_new_i, (st_data_t)args);
+ if (ret == -1) {
+ tbl = RHASH_TBL_RAW(hash);
+ ret = st_update(tbl, (st_data_t)key, add_new_i, (st_data_t)val);
+ }
+ if (!ret) {
+ // Newly inserted
+ RB_OBJ_WRITTEN(hash, Qundef, key);
+ RB_OBJ_WRITTEN(hash, Qundef, val);
+ }
+ return ret;
}
static st_data_t
@@ -4815,15 +5096,6 @@ rb_hash_bulk_insert(long argc, const VALUE *argv, VALUE hash)
if (argc > 0) {
st_index_t size = argc / 2;
- if (RHASH_TABLE_NULL_P(hash)) {
- if (size <= RHASH_AR_TABLE_MAX_SIZE) {
- hash_ar_table(hash);
- }
- else {
- RHASH_TBL_RAW(hash);
- }
- }
-
if (RHASH_AR_TABLE_P(hash) &&
(RHASH_AR_TABLE_SIZE(hash) + size <= RHASH_AR_TABLE_MAX_SIZE)) {
ar_bulk_insert(hash, argc, argv);
@@ -4861,8 +5133,7 @@ extern char **environ;
#define ENVNMATCH(s1, s2, n) (memcmp((s1), (s2), (n)) == 0)
#endif
-#define ENV_LOCK() RB_VM_LOCK_ENTER()
-#define ENV_UNLOCK() RB_VM_LOCK_LEAVE()
+#define ENV_LOCKING() RB_VM_LOCKING()
static inline rb_encoding *
env_encoding(void)
@@ -4884,28 +5155,27 @@ env_enc_str_new(const char *ptr, long len, rb_encoding *enc)
}
static VALUE
-env_str_new(const char *ptr, long len)
+env_str_new(const char *ptr, long len, rb_encoding *enc)
{
- return env_enc_str_new(ptr, len, env_encoding());
+ return env_enc_str_new(ptr, len, enc);
}
static VALUE
-env_str_new2(const char *ptr)
+env_str_new2(const char *ptr, rb_encoding *enc)
{
if (!ptr) return Qnil;
- return env_str_new(ptr, strlen(ptr));
+ return env_str_new(ptr, strlen(ptr), enc);
}
static VALUE
getenv_with_lock(const char *name)
{
VALUE ret;
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
const char *val = getenv(name);
- ret = env_str_new2(val);
+ ret = env_str_new2(val, enc);
}
- ENV_UNLOCK();
return ret;
}
@@ -4914,11 +5184,9 @@ has_env_with_lock(const char *name)
{
const char *val;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
val = getenv(name);
}
- ENV_UNLOCK();
return val ? true : false;
}
@@ -4926,9 +5194,7 @@ has_env_with_lock(const char *name)
static const char TZ_ENV[] = "TZ";
static void *
-get_env_cstr(
- VALUE str,
- const char *name)
+get_env_cstr(VALUE str, const char *name)
{
char *var;
rb_encoding *enc = rb_enc_get(str);
@@ -4950,7 +5216,7 @@ static inline const char *
env_name(volatile VALUE *s)
{
const char *name;
- SafeStringValue(*s);
+ StringValue(*s);
get_env_ptr(name, *s);
return name;
}
@@ -4960,7 +5226,7 @@ env_name(volatile VALUE *s)
static VALUE env_aset(VALUE nm, VALUE val);
static void
-reset_by_modified_env(const char *nam)
+reset_by_modified_env(const char *nam, const char *val)
{
/*
* ENV['TZ'] = nil has a special meaning.
@@ -4969,7 +5235,7 @@ reset_by_modified_env(const char *nam)
* This hack might works only on Linux glibc.
*/
if (ENVMATCH(nam, TZ_ENV)) {
- ruby_reset_timezone();
+ ruby_reset_timezone(val);
}
}
@@ -4977,7 +5243,7 @@ static VALUE
env_delete(VALUE name)
{
const char *nam = env_name(name);
- reset_by_modified_env(nam);
+ reset_by_modified_env(nam, NULL);
VALUE val = getenv_with_lock(nam);
if (!NIL_P(val)) {
@@ -5123,44 +5389,6 @@ envix(const char *nam)
}
#endif
-#if defined(_WIN32)
-static size_t
-getenvsize(const WCHAR* p)
-{
- const WCHAR* porg = p;
- while (*p++) p += lstrlenW(p) + 1;
- return p - porg + 1;
-}
-
-static size_t
-getenvblocksize(void)
-{
-#ifdef _MAX_ENV
- return _MAX_ENV;
-#else
- return 32767;
-#endif
-}
-
-static int
-check_envsize(size_t n)
-{
- if (_WIN32_WINNT < 0x0600 && rb_w32_osver() < 6) {
- /* https://msdn.microsoft.com/en-us/library/windows/desktop/ms682653(v=vs.85).aspx */
- /* Windows Server 2003 and Windows XP: The maximum size of the
- * environment block for the process is 32,767 characters. */
- WCHAR* p = GetEnvironmentStringsW();
- if (!p) return -1; /* never happen */
- n += getenvsize(p);
- FreeEnvironmentStringsW(p);
- if (n >= getenvblocksize()) {
- return -1;
- }
- }
- return 0;
-}
-#endif
-
#if defined(_WIN32) || \
(defined(__sun) && !(defined(HAVE_SETENV) && defined(HAVE_UNSETENV)))
@@ -5186,9 +5414,6 @@ void
ruby_setenv(const char *name, const char *value)
{
#if defined(_WIN32)
-# if defined(MINGW_HAS_SECURE_API) || RUBY_MSVCRT_VERSION >= 80
-# define HAVE__WPUTENV_S 1
-# endif
VALUE buf;
WCHAR *wname;
WCHAR *wvalue = 0;
@@ -5199,43 +5424,30 @@ ruby_setenv(const char *name, const char *value)
if (value) {
int len2;
len2 = MultiByteToWideChar(CP_UTF8, 0, value, -1, NULL, 0);
- if (check_envsize((size_t)len + len2)) { /* len and len2 include '\0' */
- goto fail; /* 2 for '=' & '\0' */
- }
wname = ALLOCV_N(WCHAR, buf, len + len2);
wvalue = wname + len;
MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, len);
MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, len2);
-#ifndef HAVE__WPUTENV_S
- wname[len-1] = L'=';
-#endif
}
else {
wname = ALLOCV_N(WCHAR, buf, len + 1);
MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, len);
wvalue = wname + len;
*wvalue = L'\0';
-#ifndef HAVE__WPUTENV_S
- wname[len-1] = L'=';
-#endif
}
- ENV_LOCK();
- {
-#ifndef HAVE__WPUTENV_S
- failed = _wputenv(wname);
-#else
+ ENV_LOCKING() {
+ /* Use _wputenv_s() instead of SetEnvironmentVariableW() to make sure
+ * special variables like "TZ" are interpret by libc. */
failed = _wputenv_s(wname, wvalue);
-#endif
}
- ENV_UNLOCK();
ALLOCV_END(buf);
/* even if putenv() failed, clean up and try to delete the
* variable from the system area. */
if (!value || !*value) {
/* putenv() doesn't handle empty value */
- if (!SetEnvironmentVariable(name, value) &&
+ if (!SetEnvironmentVariableW(wname, value ? wvalue : NULL) &&
GetLastError() != ERROR_ENVVAR_NOT_FOUND) goto fail;
}
if (failed) {
@@ -5245,30 +5457,24 @@ ruby_setenv(const char *name, const char *value)
#elif defined(HAVE_SETENV) && defined(HAVE_UNSETENV)
if (value) {
int ret;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
ret = setenv(name, value, 1);
}
- ENV_UNLOCK();
- if (ret) rb_sys_fail_str(rb_sprintf("setenv(%s)", name));
+ if (ret) rb_sys_fail_sprintf("setenv(%s)", name);
}
else {
#ifdef VOID_UNSETENV
- ENV_LOCK();
- {
+ ENV_LOCKING() {
unsetenv(name);
}
- ENV_UNLOCK();
#else
int ret;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
ret = unsetenv(name);
}
- ENV_UNLOCK();
- if (ret) rb_sys_fail_str(rb_sprintf("unsetenv(%s)", name));
+ if (ret) rb_sys_fail_sprintf("unsetenv(%s)", name);
#endif
}
#elif defined __sun
@@ -5285,12 +5491,11 @@ ruby_setenv(const char *name, const char *value)
mem_size = len + strlen(value) + 2;
mem_ptr = malloc(mem_size);
if (mem_ptr == NULL)
- rb_sys_fail_str(rb_sprintf("malloc(%"PRIuSIZE")", mem_size));
+ rb_sys_fail_sprintf("malloc(%"PRIuSIZE")", mem_size);
snprintf(mem_ptr, mem_size, "%s=%s", name, value);
}
- ENV_LOCK();
- {
+ ENV_LOCKING() {
for (env_ptr = GET_ENVIRON(environ); (str = *env_ptr) != 0; ++env_ptr) {
if (!strncmp(str, name, len) && str[len] == '=') {
if (!in_origenv(str)) free(str);
@@ -5299,27 +5504,23 @@ ruby_setenv(const char *name, const char *value)
}
}
}
- ENV_UNLOCK();
if (value) {
int ret;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
ret = putenv(mem_ptr);
}
- ENV_UNLOCK();
if (ret) {
free(mem_ptr);
- rb_sys_fail_str(rb_sprintf("putenv(%s)", name));
+ rb_sys_fail_sprintf("putenv(%s)", name);
}
}
#else /* WIN32 */
size_t len;
int i;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
i = envix(name); /* where does it go? */
if (environ == origenviron) { /* need we copy environment? */
@@ -5360,7 +5561,6 @@ ruby_setenv(const char *name, const char *value)
finish:;
}
- ENV_UNLOCK();
#endif /* WIN32 */
}
@@ -5375,8 +5575,6 @@ ruby_unsetenv(const char *name)
* ENV[name] = value -> value
* ENV.store(name, value) -> value
*
- * ENV.store is an alias for ENV.[]=.
- *
* Creates, updates, or deletes the named environment variable, returning the value.
* Both +name+ and +value+ may be instances of String.
* See {Valid Names and Values}[rdoc-ref:ENV@Valid+Names+and+Values].
@@ -5429,15 +5627,15 @@ env_aset(VALUE nm, VALUE val)
env_delete(nm);
return Qnil;
}
- SafeStringValue(nm);
- SafeStringValue(val);
+ StringValue(nm);
+ StringValue(val);
/* nm can be modified in `val.to_str`, don't get `name` before
* check for `val` */
get_env_ptr(name, nm);
get_env_ptr(value, val);
ruby_setenv(name, value);
- reset_by_modified_env(name);
+ reset_by_modified_env(name, value);
return val;
}
@@ -5447,8 +5645,7 @@ env_keys(int raw)
rb_encoding *enc = raw ? 0 : rb_locale_encoding();
VALUE ary = rb_ary_new();
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
@@ -5462,7 +5659,6 @@ env_keys(int raw)
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return ary;
}
@@ -5492,8 +5688,7 @@ rb_env_size(VALUE ehash, VALUE args, VALUE eobj)
char **env;
long cnt = 0;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
env = GET_ENVIRON(environ);
for (; *env ; ++env) {
if (strchr(*env, '=')) {
@@ -5502,7 +5697,6 @@ rb_env_size(VALUE ehash, VALUE args, VALUE eobj)
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return LONG2FIX(cnt);
}
@@ -5543,20 +5737,19 @@ env_values(void)
{
VALUE ary = rb_ary_new();
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
- rb_ary_push(ary, env_str_new2(s+1));
+ rb_ary_push(ary, env_str_new2(s+1, enc));
}
env++;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return ary;
}
@@ -5617,7 +5810,7 @@ env_each_value(VALUE ehash)
* ENV.each_pair { |name, value| block } -> ENV
* ENV.each_pair -> an_enumerator
*
- * Yields each environment variable name and its value as a 2-element \Array:
+ * Yields each environment variable name and its value as a 2-element Array:
* h = {}
* ENV.each_pair { |name, value| h[name] = value } # => ENV
* h # => {"bar"=>"1", "foo"=>"0"}
@@ -5637,21 +5830,20 @@ env_each_pair(VALUE ehash)
VALUE ary = rb_ary_new();
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
- rb_ary_push(ary, env_str_new(*env, s-*env));
- rb_ary_push(ary, env_str_new2(s+1));
+ rb_ary_push(ary, env_str_new(*env, s-*env, enc));
+ rb_ary_push(ary, env_str_new2(s+1, enc));
}
env++;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
if (rb_block_pair_yield_optimizable()) {
for (i=0; i<RARRAY_LEN(ary); i+=2) {
@@ -5753,7 +5945,7 @@ env_delete_if(VALUE ehash)
* Returns +nil+ in the Array for each name that is not an ENV name:
* ENV.values_at('foo', 'bat', 'bar', 'bam') # => ["0", nil, "1", nil]
*
- * Returns an empty \Array if no names given.
+ * Returns an empty Array if no names given.
*
* Raises an exception if any name is invalid.
* See {Invalid Names and Values}[rdoc-ref:ENV@Invalid+Names+and+Values].
@@ -5778,8 +5970,6 @@ env_values_at(int argc, VALUE *argv, VALUE _)
* ENV.filter { |name, value| block } -> hash of name/value pairs
* ENV.filter -> an_enumerator
*
- * ENV.filter is an alias for ENV.select.
- *
* Yields each environment variable name and its value as a 2-element Array,
* returning a Hash of the names and values for which the block returns a truthy value:
* ENV.replace('foo' => '0', 'bar' => '1', 'baz' => '2')
@@ -5823,8 +6013,6 @@ env_select(VALUE ehash)
* ENV.filter! { |name, value| block } -> ENV or nil
* ENV.filter! -> an_enumerator
*
- * ENV.filter! is an alias for ENV.select!.
- *
* Yields each environment variable name and its value as a 2-element Array,
* deleting each entry for which the block returns +false+ or +nil+,
* and returning ENV if any deletions made, or +nil+ otherwise:
@@ -5992,30 +6180,27 @@ env_to_s(VALUE _)
static VALUE
env_inspect(VALUE _)
{
- VALUE i;
VALUE str = rb_str_buf_new2("{");
+ rb_encoding *enc = env_encoding();
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
- char *s = strchr(*env, '=');
+ const char *s = strchr(*env, '=');
if (env != environ) {
rb_str_buf_cat2(str, ", ");
}
if (s) {
- rb_str_buf_cat2(str, "\"");
- rb_str_buf_cat(str, *env, s-*env);
- rb_str_buf_cat2(str, "\"=>");
- i = rb_inspect(rb_str_new2(s+1));
- rb_str_buf_append(str, i);
+ rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(*env, s-*env, enc)));
+ rb_str_buf_cat2(str, " => ");
+ s++;
+ rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(s, strlen(s), enc)));
}
env++;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
rb_str_buf_cat2(str, "}");
@@ -6036,20 +6221,19 @@ env_to_a(VALUE _)
{
VALUE ary = rb_ary_new();
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
- rb_ary_push(ary, rb_assoc_new(env_str_new(*env, s-*env),
- env_str_new2(s+1)));
+ rb_ary_push(ary, rb_assoc_new(env_str_new(*env, s-*env, enc),
+ env_str_new2(s+1, enc)));
}
env++;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return ary;
}
@@ -6073,13 +6257,11 @@ env_size_with_lock(void)
{
int i = 0;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (env[i]) i++;
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return i;
}
@@ -6115,15 +6297,13 @@ env_empty_p(VALUE _)
{
bool empty = true;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
if (env[0] != 0) {
empty = false;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return RBOOL(empty);
}
@@ -6135,8 +6315,6 @@ env_empty_p(VALUE _)
* ENV.member?(name) -> true or false
* ENV.key?(name) -> true or false
*
- * ENV.has_key?, ENV.member?, and ENV.key? are aliases for ENV.include?.
- *
* Returns +true+ if there is an environment variable with the given +name+:
* ENV.replace('foo' => '0', 'bar' => '1')
* ENV.include?('foo') # => true
@@ -6214,8 +6392,7 @@ env_has_value(VALUE dmy, VALUE obj)
VALUE ret = Qfalse;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
@@ -6230,7 +6407,6 @@ env_has_value(VALUE dmy, VALUE obj)
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return ret;
}
@@ -6257,8 +6433,7 @@ env_rassoc(VALUE dmy, VALUE obj)
VALUE result = Qnil;
- ENV_LOCK();
- {
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
@@ -6275,7 +6450,6 @@ env_rassoc(VALUE dmy, VALUE obj)
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return result;
}
@@ -6299,18 +6473,18 @@ env_rassoc(VALUE dmy, VALUE obj)
static VALUE
env_key(VALUE dmy, VALUE value)
{
- SafeStringValue(value);
+ StringValue(value);
VALUE str = Qnil;
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s++) {
long len = strlen(s);
if (RSTRING_LEN(value) == len && strncmp(s, RSTRING_PTR(value), len) == 0) {
- str = env_str_new(*env, s-*env-1);
+ str = env_str_new(*env, s-*env-1, enc);
break;
}
}
@@ -6318,7 +6492,6 @@ env_key(VALUE dmy, VALUE value)
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return str;
}
@@ -6328,20 +6501,19 @@ env_to_hash(void)
{
VALUE hash = rb_hash_new();
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
- rb_hash_aset(hash, env_str_new(*env, s-*env),
- env_str_new2(s+1));
+ rb_hash_aset(hash, env_str_new(*env, s-*env, enc),
+ env_str_new2(s+1, enc));
}
env++;
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
return hash;
}
@@ -6385,7 +6557,7 @@ env_f_to_hash(VALUE _)
* Each name/value pair in ENV is yielded to the block.
* The block must return a 2-element Array (name/value pair)
* that is added to the return Hash as a key and value:
- * ENV.to_h { |name, value| [name.to_sym, value.to_i] } # => {:bar=>1, :foo=>0}
+ * ENV.to_h { |name, value| [name.to_sym, value.to_i] } # => {bar: 1, foo: 0}
* Raises an exception if the block does not return an Array:
* ENV.to_h { |name, value| name } # Raises TypeError (wrong element type String (expected array))
* Raises an exception if the block returns an Array of the wrong size:
@@ -6481,21 +6653,20 @@ env_shift(VALUE _)
VALUE result = Qnil;
VALUE key = Qnil;
- ENV_LOCK();
- {
+ rb_encoding *enc = env_encoding();
+ ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
if (*env) {
const char *p = *env;
char *s = strchr(p, '=');
if (s) {
- key = env_str_new(p, s-p);
- VALUE val = env_str_new2(getenv(RSTRING_PTR(key)));
+ key = env_str_new(p, s-p, enc);
+ VALUE val = env_str_new2(getenv(RSTRING_PTR(key)), enc);
result = rb_assoc_new(key, val);
}
}
FREE_ENVIRON(environ);
}
- ENV_UNLOCK();
if (!NIL_P(key)) {
env_delete(key);
@@ -6617,8 +6788,6 @@ env_update_block_i(VALUE key, VALUE val, VALUE _)
* ENV.merge!(*hashes) -> ENV
* ENV.merge!(*hashes) { |name, env_val, hash_val| block } -> ENV
*
- * ENV.update is an alias for ENV.merge!.
- *
* Adds to ENV each key/value pair in the given +hash+; returns ENV:
* ENV.replace('foo' => '0', 'bar' => '1')
* ENV.merge!('baz' => '2', 'bat' => '3') # => {"bar"=>"1", "bat"=>"3", "baz"=>"2", "foo"=>"0"}
@@ -6708,70 +6877,71 @@ static const rb_data_type_t env_data_type = {
};
/*
- * A \Hash maps each of its unique keys to a specific value.
+ * A \Hash object maps each of its unique keys to a specific value.
+ *
+ * A hash has certain similarities to an Array, but:
*
- * A \Hash has certain similarities to an \Array, but:
- * - An \Array index is always an \Integer.
- * - A \Hash key can be (almost) any object.
+ * - An array index is always an integer.
+ * - A hash key can be (almost) any object.
*
* === \Hash \Data Syntax
*
- * The older syntax for \Hash data uses the "hash rocket," <tt>=></tt>:
+ * The original syntax for a hash entry uses the "hash rocket," <tt>=></tt>:
*
* h = {:foo => 0, :bar => 1, :baz => 2}
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
*
- * Alternatively, but only for a \Hash key that's a \Symbol,
+ * Alternatively, but only for a key that's a symbol,
* you can use a newer JSON-style syntax,
- * where each bareword becomes a \Symbol:
+ * where each bareword becomes a symbol:
*
* h = {foo: 0, bar: 1, baz: 2}
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
*
- * You can also use a \String in place of a bareword:
+ * You can also use a string in place of a bareword:
*
* h = {'foo': 0, 'bar': 1, 'baz': 2}
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
*
* And you can mix the styles:
*
* h = {foo: 0, :bar => 1, 'baz': 2}
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
*
* But it's an error to try the JSON-style syntax
- * for a key that's not a bareword or a String:
+ * for a key that's not a bareword or a string:
*
* # Raises SyntaxError (syntax error, unexpected ':', expecting =>):
* h = {0: 'zero'}
*
- * Hash value can be omitted, meaning that value will be fetched from the context
+ * The value can be omitted, meaning that value will be fetched from the context
* by the name of the key:
*
* x = 0
* y = 100
* h = {x:, y:}
- * h # => {:x=>0, :y=>100}
+ * h # => {x: 0, y: 100}
*
* === Common Uses
*
- * You can use a \Hash to give names to objects:
+ * You can use a hash to give names to objects:
*
* person = {name: 'Matz', language: 'Ruby'}
- * person # => {:name=>"Matz", :language=>"Ruby"}
+ * person # => {name: "Matz", language: "Ruby"}
*
- * You can use a \Hash to give names to method arguments:
+ * You can use a hash to give names to method arguments:
*
* def some_method(hash)
* p hash
* end
- * some_method({foo: 0, bar: 1, baz: 2}) # => {:foo=>0, :bar=>1, :baz=>2}
+ * some_method({foo: 0, bar: 1, baz: 2}) # => {foo: 0, bar: 1, baz: 2}
*
- * Note: when the last argument in a method call is a \Hash,
+ * Note: when the last argument in a method call is a hash,
* the curly braces may be omitted:
*
- * some_method(foo: 0, bar: 1, baz: 2) # => {:foo=>0, :bar=>1, :baz=>2}
+ * some_method(foo: 0, bar: 1, baz: 2) # => {foo: 0, bar: 1, baz: 2}
*
- * You can use a \Hash to initialize an object:
+ * You can use a hash to initialize an object:
*
* class Dev
* attr_accessor :name, :language
@@ -6789,63 +6959,55 @@ static const rb_data_type_t env_data_type = {
*
* - A {hash literal}[rdoc-ref:syntax/literals.rdoc@Hash+Literals].
*
- * You can convert certain objects to Hashes with:
+ * You can convert certain objects to hashes with:
*
- * - \Method #Hash.
+ * - Method Kernel#Hash.
*
- * You can create a \Hash by calling method Hash.new.
- *
- * Create an empty Hash:
+ * You can create a hash by calling method Hash.new:
*
+ * # Create an empty hash.
* h = Hash.new
* h # => {}
* h.class # => Hash
*
- * You can create a \Hash by calling method Hash.[].
- *
- * Create an empty Hash:
+ * You can create a hash by calling method Hash.[]:
*
+ * # Create an empty hash.
* h = Hash[]
* h # => {}
- *
- * Create a \Hash with initial entries:
- *
+ * # Create a hash with initial entries.
* h = Hash[foo: 0, bar: 1, baz: 2]
- * h # => {:foo=>0, :bar=>1, :baz=>2}
- *
- * You can create a \Hash by using its literal form (curly braces).
+ * h # => {foo: 0, bar: 1, baz: 2}
*
- * Create an empty \Hash:
+ * You can create a hash by using its literal form (curly braces):
*
+ * # Create an empty hash.
* h = {}
* h # => {}
- *
- * Create a \Hash with initial entries:
- *
+ * # Create a +Hash+ with initial entries.
* h = {foo: 0, bar: 1, baz: 2}
- * h # => {:foo=>0, :bar=>1, :baz=>2}
- *
+ * h # => {foo: 0, bar: 1, baz: 2}
*
* === \Hash Value Basics
*
- * The simplest way to retrieve a \Hash value (instance method #[]):
+ * The simplest way to retrieve a hash value (instance method #[]):
*
* h = {foo: 0, bar: 1, baz: 2}
* h[:foo] # => 0
*
- * The simplest way to create or update a \Hash value (instance method #[]=):
+ * The simplest way to create or update a hash value (instance method #[]=):
*
* h = {foo: 0, bar: 1, baz: 2}
* h[:bat] = 3 # => 3
- * h # => {:foo=>0, :bar=>1, :baz=>2, :bat=>3}
+ * h # => {foo: 0, bar: 1, baz: 2, bat: 3}
* h[:foo] = 4 # => 4
- * h # => {:foo=>4, :bar=>1, :baz=>2, :bat=>3}
+ * h # => {foo: 4, bar: 1, baz: 2, bat: 3}
*
- * The simplest way to delete a \Hash entry (instance method #delete):
+ * The simplest way to delete a hash entry (instance method #delete):
*
* h = {foo: 0, bar: 1, baz: 2}
* h.delete(:bar) # => 1
- * h # => {:foo=>0, :baz=>2}
+ * h # => {foo: 0, baz: 2}
*
* === Entry Order
*
@@ -6853,41 +7015,41 @@ static const rb_data_type_t env_data_type = {
*
* - Iterative methods such as <tt>each</tt>, <tt>each_key</tt>, <tt>each_pair</tt>, <tt>each_value</tt>.
* - Other order-sensitive methods such as <tt>shift</tt>, <tt>keys</tt>, <tt>values</tt>.
- * - The \String returned by method <tt>inspect</tt>.
+ * - The string returned by method <tt>inspect</tt>.
*
- * A new \Hash has its initial ordering per the given entries:
+ * A new hash has its initial ordering per the given entries:
*
* h = Hash[foo: 0, bar: 1]
- * h # => {:foo=>0, :bar=>1}
+ * h # => {foo: 0, bar: 1}
*
* New entries are added at the end:
*
* h[:baz] = 2
- * h # => {:foo=>0, :bar=>1, :baz=>2}
+ * h # => {foo: 0, bar: 1, baz: 2}
*
* Updating a value does not affect the order:
*
* h[:baz] = 3
- * h # => {:foo=>0, :bar=>1, :baz=>3}
+ * h # => {foo: 0, bar: 1, baz: 3}
*
* But re-creating a deleted entry can affect the order:
*
* h.delete(:foo)
* h[:foo] = 5
- * h # => {:bar=>1, :baz=>3, :foo=>5}
+ * h # => {bar: 1, baz: 3, foo: 5}
*
- * === \Hash Keys
+ * === +Hash+ Keys
*
- * ==== \Hash Key Equivalence
+ * ==== +Hash+ Key Equivalence
*
* Two objects are treated as the same \hash key when their <code>hash</code> value
* is identical and the two objects are <code>eql?</code> to each other.
*
- * ==== Modifying an Active \Hash Key
+ * ==== Modifying an Active +Hash+ Key
*
- * Modifying a \Hash key while it is in use damages the hash's index.
+ * Modifying a +Hash+ key while it is in use damages the hash's index.
*
- * This \Hash has keys that are Arrays:
+ * This +Hash+ has keys that are Arrays:
*
* a0 = [ :foo, :bar ]
* a1 = [ :baz, :bat ]
@@ -6901,7 +7063,7 @@ static const rb_data_type_t env_data_type = {
* a0[0] = :bam
* a0.hash # => 1069447059
*
- * And damages the \Hash index:
+ * And damages the +Hash+ index:
*
* h.include?(a0) # => false
* h[a0] # => nil
@@ -6912,9 +7074,9 @@ static const rb_data_type_t env_data_type = {
* h.include?(a0) # => true
* h[a0] # => 0
*
- * A \String key is always safe.
- * That's because an unfrozen \String
- * passed as a key will be replaced by a duplicated and frozen \String:
+ * A String key is always safe.
+ * That's because an unfrozen String
+ * passed as a key will be replaced by a duplicated and frozen String:
*
* s = 'foo'
* s.frozen? # => false
@@ -6922,15 +7084,15 @@ static const rb_data_type_t env_data_type = {
* first_key = h.keys.first
* first_key.frozen? # => true
*
- * ==== User-Defined \Hash Keys
+ * ==== User-Defined +Hash+ Keys
*
- * To be useable as a \Hash key, objects must implement the methods <code>hash</code> and <code>eql?</code>.
- * Note: this requirement does not apply if the \Hash uses #compare_by_identity since comparison will then
+ * To be usable as a +Hash+ key, objects must implement the methods <code>hash</code> and <code>eql?</code>.
+ * Note: this requirement does not apply if the +Hash+ uses #compare_by_identity since comparison will then
* rely on the keys' object id instead of <code>hash</code> and <code>eql?</code>.
*
- * \Object defines basic implementation for <code>hash</code> and <code>eq?</code> that makes each object
+ * Object defines basic implementation for <code>hash</code> and <code>eq?</code> that makes each object
* a distinct key. Typically, user-defined classes will want to override these methods to provide meaningful
- * behavior, or for example inherit \Struct that has useful definitions for these.
+ * behavior, or for example inherit Struct that has useful definitions for these.
*
* A typical implementation of <code>hash</code> is based on the
* object's data while <code>eql?</code> is usually aliased to the overridden
@@ -6953,7 +7115,7 @@ static const rb_data_type_t env_data_type = {
* alias eql? ==
*
* def hash
- * @author.hash ^ @title.hash # XOR
+ * [self.class, @author, @title].hash
* end
* end
*
@@ -6967,99 +7129,98 @@ static const rb_data_type_t env_data_type = {
*
* reviews.length #=> 1
*
- * === Default Values
+ * === Key Not Found?
*
- * The methods #[], #values_at and #dig need to return the value associated to a certain key.
- * When that key is not found, that value will be determined by its default proc (if any)
- * or else its default (initially `nil`).
+ * When a method tries to retrieve and return the value for a key and that key <i>is found</i>,
+ * the returned value is the value associated with the key.
*
- * You can retrieve the default value with method #default:
+ * But what if the key <i>is not found</i>?
+ * In that case, certain methods will return a default value while other will raise a \KeyError.
*
- * h = Hash.new
- * h.default # => nil
+ * ==== Nil Return Value
*
- * You can set the default value by passing an argument to method Hash.new or
- * with method #default=
+ * If you want +nil+ returned for a not-found key, you can call:
*
- * h = Hash.new(-1)
- * h.default # => -1
- * h.default = 0
- * h.default # => 0
+ * - #[](key) (usually written as <tt>#[key]</tt>.
+ * - #assoc(key).
+ * - #dig(key, *identifiers).
+ * - #values_at(*keys).
*
- * This default value is returned for #[], #values_at and #dig when a key is
- * not found:
+ * You can override these behaviors for #[], #dig, and #values_at (but not #assoc);
+ * see {Hash Default}[rdoc-ref:Hash@Hash+Default].
*
- * counts = {foo: 42}
- * counts.default # => nil (default)
- * counts[:foo] = 42
- * counts[:bar] # => nil
- * counts.default = 0
- * counts[:bar] # => 0
- * counts.values_at(:foo, :bar, :baz) # => [42, 0, 0]
- * counts.dig(:bar) # => 0
+ * ==== \KeyError
*
- * Note that the default value is used without being duplicated. It is not advised to set
- * the default value to a mutable object:
+ * If you want KeyError raised for a not-found key, you can call:
*
- * synonyms = Hash.new([])
- * synonyms[:hello] # => []
- * synonyms[:hello] << :hi # => [:hi], but this mutates the default!
- * synonyms.default # => [:hi]
- * synonyms[:world] << :universe
- * synonyms[:world] # => [:hi, :universe], oops
- * synonyms.keys # => [], oops
+ * - #fetch(key).
+ * - #fetch_values(*keys).
*
- * To use a mutable object as default, it is recommended to use a default proc
+ * ==== \Hash Default
*
- * ==== Default \Proc
+ * For certain methods (#[], #dig, and #values_at),
+ * the return value for a not-found key is determined by two hash properties:
*
- * When the default proc for a \Hash is set (i.e., not +nil+),
- * the default value returned by method #[] is determined by the default proc alone.
+ * - <i>default value</i>: returned by method #default.
+ * - <i>default proc</i>: returned by method #default_proc.
*
- * You can retrieve the default proc with method #default_proc:
+ * In the simple case, both values are +nil+,
+ * and the methods return +nil+ for a not-found key;
+ * see {Nil Return Value}[rdoc-ref:Hash@Nil+Return+Value] above.
*
- * h = Hash.new
- * h.default_proc # => nil
+ * Note that this entire section ("Hash Default"):
*
- * You can set the default proc by calling Hash.new with a block or
- * calling the method #default_proc=
+ * - Applies _only_ to methods #[], #dig, and #values_at.
+ * - Does _not_ apply to methods #assoc, #fetch, or #fetch_values,
+ * which are not affected by the default value or default proc.
*
- * h = Hash.new { |hash, key| "Default value for #{key}" }
- * h.default_proc.class # => Proc
- * h.default_proc = proc { |hash, key| "Default value for #{key.inspect}" }
- * h.default_proc.class # => Proc
+ * ===== Any-Key Default
+ *
+ * You can define an any-key default for a hash;
+ * that is, a value that will be returned for _any_ not-found key:
+ *
+ * - The value of #default_proc <i>must be</i> +nil+.
+ * - The value of #default (which may be any object, including +nil+)
+ * will be returned for a not-found key.
*
- * When the default proc is set (i.e., not +nil+)
- * and method #[] is called with with a non-existent key,
- * #[] calls the default proc with both the \Hash object itself and the missing key,
- * then returns the proc's return value:
+ * You can set the default value when the hash is created with Hash.new and option +default_value+,
+ * or later with method #default=.
*
- * h = Hash.new { |hash, key| "Default value for #{key}" }
- * h[:nosuch] # => "Default value for nosuch"
+ * Note: although the value of #default may be any object,
+ * it may not be a good idea to use a mutable object.
*
- * Note that in the example above no entry for key +:nosuch+ is created:
+ * ===== Per-Key Defaults
*
- * h.include?(:nosuch) # => false
+ * You can define a per-key default for a hash;
+ * that is, a Proc that will return a value based on the key itself.
*
- * However, the proc itself can add a new entry:
+ * You can set the default proc when the hash is created with Hash.new and a block,
+ * or later with method #default_proc=.
*
- * synonyms = Hash.new { |hash, key| hash[key] = [] }
- * synonyms.include?(:hello) # => false
- * synonyms[:hello] << :hi # => [:hi]
- * synonyms[:world] << :universe # => [:universe]
- * synonyms.keys # => [:hello, :world]
+ * Note that the proc can modify +self+,
+ * but modifying +self+ in this way is not thread-safe;
+ * multiple threads can concurrently call into the default proc
+ * for the same key.
*
- * Note that setting the default proc will clear the default value and vice versa.
+ * ==== \Method Default
+ *
+ * For two methods, you can specify a default value for a not-found key
+ * that has effect only for a single method call
+ * (and not for any subsequent calls):
+ *
+ * - For method #fetch, you can specify an any-key default:
+ * - For either method #fetch or method #fetch_values,
+ * you can specify a per-key default via a block.
*
* === What's Here
*
- * First, what's elsewhere. \Class \Hash:
+ * First, what's elsewhere. Class +Hash+:
*
* - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
* - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
* which provides dozens of additional methods.
*
- * Here, class \Hash provides methods that are useful for:
+ * Here, class +Hash+ provides methods that are useful for:
*
* - {Creating a Hash}[rdoc-ref:Hash@Methods+for+Creating+a+Hash]
* - {Setting Hash State}[rdoc-ref:Hash@Methods+for+Setting+Hash+State]
@@ -7071,17 +7232,16 @@ static const rb_data_type_t env_data_type = {
* - {Iterating}[rdoc-ref:Hash@Methods+for+Iterating]
* - {Converting}[rdoc-ref:Hash@Methods+for+Converting]
* - {Transforming Keys and Values}[rdoc-ref:Hash@Methods+for+Transforming+Keys+and+Values]
- * - {And more....}[rdoc-ref:Hash@Other+Methods]
*
- * \Class \Hash also includes methods from module Enumerable.
+ * Class +Hash+ also includes methods from module Enumerable.
*
- * ==== Methods for Creating a \Hash
+ * ==== Methods for Creating a +Hash+
*
* - ::[]: Returns a new hash populated with given objects.
* - ::new: Returns a new empty hash.
* - ::try_convert: Returns a new hash created from a given object.
*
- * ==== Methods for Setting \Hash State
+ * ==== Methods for Setting +Hash+ State
*
* - #compare_by_identity: Sets +self+ to consider only identity in comparing keys.
* - #default=: Sets the default to a given value.
@@ -7097,10 +7257,9 @@ static const rb_data_type_t env_data_type = {
* - #empty?: Returns whether there are no entries.
* - #eql?: Returns whether a given object is equal to +self+.
* - #hash: Returns the integer hash code.
- * - #has_value?: Returns whether a given object is a value in +self+.
- * - #include?, #has_key?, #member?, #key?: Returns whether a given object is a key in +self+.
- * - #length, #size: Returns the count of entries.
- * - #value?: Returns whether a given object is a value in +self+.
+ * - #has_value? (aliased as #value?): Returns whether a given object is a value in +self+.
+ * - #include? (aliased as #has_key?, #member?, #key?): Returns whether a given object is a key in +self+.
+ * - #size (aliased as #length): Returns the count of entries.
*
* ==== Methods for Comparing
*
@@ -7108,7 +7267,7 @@ static const rb_data_type_t env_data_type = {
* - #<=: Returns whether +self+ is a subset of a given object.
* - #==: Returns whether a given object is equal to +self+.
* - #>: Returns whether +self+ is a proper superset of a given object
- * - #>=: Returns whether +self+ is a proper superset of a given object.
+ * - #>=: Returns whether +self+ is a superset of a given object.
*
* ==== Methods for Fetching
*
@@ -7121,16 +7280,16 @@ static const rb_data_type_t env_data_type = {
* - #key: Returns the key for the first-found entry with a given value.
* - #keys: Returns an array containing all keys in +self+.
* - #rassoc: Returns a 2-element array consisting of the key and value
- of the first-found entry having a given value.
- * - #values: Returns an array containing all values in +self+/
+ * of the first-found entry having a given value.
+ * - #values: Returns an array containing all values in +self+.
* - #values_at: Returns an array containing values for given keys.
*
* ==== Methods for Assigning
*
- * - #[]=, #store: Associates a given key with a given value.
+ * - #[]= (aliased as #store): Associates a given key with a given value.
* - #merge: Returns the hash formed by merging each given hash into a copy of +self+.
- * - #merge!, #update: Merges each given hash into +self+.
- * - #replace: Replaces the entire contents of +self+ with the contents of a given hash.
+ * - #update (aliased as #merge!): Merges each given hash into +self+.
+ * - #replace (aliased as #initialize_copy): Replaces the entire contents of +self+ with the contents of a given hash.
*
* ==== Methods for Deleting
*
@@ -7140,7 +7299,7 @@ static const rb_data_type_t env_data_type = {
* - #compact!: Removes all +nil+-valued entries from +self+.
* - #delete: Removes the entry for a given key.
* - #delete_if: Removes entries selected by a given block.
- * - #filter!, #select!: Keep only those entries selected by a given block.
+ * - #select! (aliased as #filter!): Keep only those entries selected by a given block.
* - #keep_if: Keep only those entries selected by a given block.
* - #reject!: Removes entries selected by a given block.
* - #shift: Removes and returns the first entry.
@@ -7149,36 +7308,34 @@ static const rb_data_type_t env_data_type = {
*
* - #compact: Returns a copy of +self+ with all +nil+-valued entries removed.
* - #except: Returns a copy of +self+ with entries removed for specified keys.
- * - #filter, #select: Returns a copy of +self+ with only those entries selected by a given block.
+ * - #select (aliased as #filter): Returns a copy of +self+ with only those entries selected by a given block.
* - #reject: Returns a copy of +self+ with entries removed as specified by a given block.
* - #slice: Returns a hash containing the entries for given keys.
*
* ==== Methods for Iterating
- * - #each, #each_pair: Calls a given block with each key-value pair.
+ * - #each_pair (aliased as #each): Calls a given block with each key-value pair.
* - #each_key: Calls a given block with each key.
* - #each_value: Calls a given block with each value.
*
* ==== Methods for Converting
*
- * - #inspect, #to_s: Returns a new String containing the hash entries.
+ * - #flatten: Returns an array that is a 1-dimensional flattening of +self+.
+ * - #inspect (aliased as #to_s): Returns a new String containing the hash entries.
* - #to_a: Returns a new array of 2-element arrays;
* each nested array contains a key-value pair from +self+.
- * - #to_h: Returns +self+ if a \Hash;
- * if a subclass of \Hash, returns a \Hash containing the entries from +self+.
+ * - #to_h: Returns +self+ if a +Hash+;
+ * if a subclass of +Hash+, returns a +Hash+ containing the entries from +self+.
* - #to_hash: Returns +self+.
* - #to_proc: Returns a proc that maps a given key to its value.
*
* ==== Methods for Transforming Keys and Values
*
+ * - #invert: Returns a hash with the each key-value pair inverted.
* - #transform_keys: Returns a copy of +self+ with modified keys.
* - #transform_keys!: Modifies keys in +self+
* - #transform_values: Returns a copy of +self+ with modified values.
* - #transform_values!: Modifies values in +self+.
*
- * ==== Other Methods
- * - #flatten: Returns an array that is a 1-dimensional flattening of +self+.
- * - #invert: Returns a hash with the each key-value pair inverted.
- *
*/
void
@@ -7195,9 +7352,9 @@ Init_Hash(void)
rb_define_alloc_func(rb_cHash, empty_hash_alloc);
rb_define_singleton_method(rb_cHash, "[]", rb_hash_s_create, -1);
rb_define_singleton_method(rb_cHash, "try_convert", rb_hash_s_try_convert, 1);
- rb_define_method(rb_cHash, "initialize", rb_hash_initialize, -1);
rb_define_method(rb_cHash, "initialize_copy", rb_hash_replace, 1);
rb_define_method(rb_cHash, "rehash", rb_hash_rehash, 0);
+ rb_define_method(rb_cHash, "freeze", rb_hash_freeze, 0);
rb_define_method(rb_cHash, "to_hash", rb_hash_to_hash, 0);
rb_define_method(rb_cHash, "to_h", rb_hash_to_h, 0);
@@ -7284,17 +7441,21 @@ Init_Hash(void)
rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash?", rb_hash_s_ruby2_keywords_hash_p, 1);
rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash", rb_hash_s_ruby2_keywords_hash, 1);
+ rb_cHash_empty_frozen = rb_hash_freeze(rb_hash_new());
+ RB_OBJ_SET_SHAREABLE(rb_cHash_empty_frozen);
+ rb_vm_register_global_object(rb_cHash_empty_frozen);
+
/* Document-class: ENV
*
- * ENV is a hash-like accessor for environment variables.
+ * +ENV+ is a hash-like accessor for environment variables.
*
* === Interaction with the Operating System
*
- * The ENV object interacts with the operating system's environment variables:
+ * The +ENV+ object interacts with the operating system's environment variables:
*
- * - When you get the value for a name in ENV, the value is retrieved from among the current environment variables.
- * - When you create or set a name-value pair in ENV, the name and value are immediately set in the environment variables.
- * - When you delete a name-value pair in ENV, it is immediately deleted from the environment variables.
+ * - When you get the value for a name in +ENV+, the value is retrieved from among the current environment variables.
+ * - When you create or set a name-value pair in +ENV+, the name and value are immediately set in the environment variables.
+ * - When you delete a name-value pair in +ENV+, it is immediately deleted from the environment variables.
*
* === Names and Values
*
@@ -7344,33 +7505,33 @@ Init_Hash(void)
*
* === About Ordering
*
- * ENV enumerates its name/value pairs in the order found
+ * +ENV+ enumerates its name/value pairs in the order found
* in the operating system's environment variables.
- * Therefore the ordering of ENV content is OS-dependent, and may be indeterminate.
+ * Therefore the ordering of +ENV+ content is OS-dependent, and may be indeterminate.
*
* This will be seen in:
- * - A Hash returned by an ENV method.
- * - An Enumerator returned by an ENV method.
+ * - A Hash returned by an +ENV+ method.
+ * - An Enumerator returned by an +ENV+ method.
* - An Array returned by ENV.keys, ENV.values, or ENV.to_a.
* - The String returned by ENV.inspect.
* - The Array returned by ENV.shift.
* - The name returned by ENV.key.
*
* === About the Examples
- * Some methods in ENV return ENV itself. Typically, there are many environment variables.
- * It's not useful to display a large ENV in the examples here,
- * so most example snippets begin by resetting the contents of ENV:
- * - ENV.replace replaces ENV with a new collection of entries.
- * - ENV.clear empties ENV.
+ * Some methods in +ENV+ return +ENV+ itself. Typically, there are many environment variables.
+ * It's not useful to display a large +ENV+ in the examples here,
+ * so most example snippets begin by resetting the contents of +ENV+:
+ * - ENV.replace replaces +ENV+ with a new collection of entries.
+ * - ENV.clear empties +ENV+.
*
- * == What's Here
+ * === What's Here
*
- * First, what's elsewhere. \Class \ENV:
+ * First, what's elsewhere. Class +ENV+:
*
* - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
* - Extends {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
*
- * Here, class \ENV provides methods that are useful for:
+ * Here, class +ENV+ provides methods that are useful for:
*
* - {Querying}[rdoc-ref:ENV@Methods+for+Querying]
* - {Assigning}[rdoc-ref:ENV@Methods+for+Assigning]
@@ -7379,26 +7540,26 @@ Init_Hash(void)
* - {Converting}[rdoc-ref:ENV@Methods+for+Converting]
* - {And more ....}[rdoc-ref:ENV@More+Methods]
*
- * === Methods for Querying
+ * ==== Methods for Querying
*
* - ::[]: Returns the value for the given environment variable name if it exists:
- * - ::empty?: Returns whether \ENV is empty.
- * - ::has_value?, ::value?: Returns whether the given value is in \ENV.
+ * - ::empty?: Returns whether +ENV+ is empty.
+ * - ::has_value?, ::value?: Returns whether the given value is in +ENV+.
* - ::include?, ::has_key?, ::key?, ::member?: Returns whether the given name
- is in \ENV.
+ is in +ENV+.
* - ::key: Returns the name of the first entry with the given value.
* - ::size, ::length: Returns the number of entries.
* - ::value?: Returns whether any entry has the given value.
*
- * === Methods for Assigning
+ * ==== Methods for Assigning
*
* - ::[]=, ::store: Creates, updates, or deletes the named environment variable.
- * - ::clear: Removes every environment variable; returns \ENV:
- * - ::update, ::merge!: Adds to \ENV each key/value pair in the given hash.
- * - ::replace: Replaces the entire content of the \ENV
+ * - ::clear: Removes every environment variable; returns +ENV+:
+ * - ::update, ::merge!: Adds to +ENV+ each key/value pair in the given hash.
+ * - ::replace: Replaces the entire content of the +ENV+
* with the name/value pairs in the given hash.
*
- * === Methods for Deleting
+ * ==== Methods for Deleting
*
* - ::delete: Deletes the named environment variable name if it exists.
* - ::delete_if: Deletes entries selected by the block.
@@ -7407,22 +7568,22 @@ Init_Hash(void)
* - ::select!, ::filter!: Deletes entries selected by the block.
* - ::shift: Removes and returns the first entry.
*
- * === Methods for Iterating
+ * ==== Methods for Iterating
*
* - ::each, ::each_pair: Calls the block with each name/value pair.
* - ::each_key: Calls the block with each name.
* - ::each_value: Calls the block with each value.
*
- * === Methods for Converting
+ * ==== Methods for Converting
*
* - ::assoc: Returns a 2-element array containing the name and value
* of the named environment variable if it exists:
- * - ::clone: Returns \ENV (and issues a warning).
+ * - ::clone: Raises an exception.
* - ::except: Returns a hash of all name/value pairs except those given.
* - ::fetch: Returns the value for the given name.
- * - ::inspect: Returns the contents of \ENV as a string.
- * - ::invert: Returns a hash whose keys are the ENV values,
- and whose values are the corresponding ENV names.
+ * - ::inspect: Returns the contents of +ENV+ as a string.
+ * - ::invert: Returns a hash whose keys are the +ENV+ values,
+ and whose values are the corresponding +ENV+ names.
* - ::keys: Returns an array of all names.
* - ::rassoc: Returns the name and value of the first found entry
* that has the given value.
@@ -7436,11 +7597,11 @@ Init_Hash(void)
* - ::values: Returns all values as an array.
* - ::values_at: Returns an array of the values for the given name.
*
- * === More Methods
+ * ==== More Methods
*
* - ::dup: Raises an exception.
* - ::freeze: Raises an exception.
- * - ::rehash: Returns +nil+, without modifying \ENV.
+ * - ::rehash: Returns +nil+, without modifying +ENV+.
*
*/
@@ -7451,8 +7612,7 @@ Init_Hash(void)
origenviron = environ;
envtbl = TypedData_Wrap_Struct(rb_cObject, &env_data_type, NULL);
rb_extend_object(envtbl, rb_mEnumerable);
- FL_SET_RAW(envtbl, RUBY_FL_SHAREABLE);
-
+ RB_OBJ_SET_SHAREABLE(envtbl);
rb_define_singleton_method(envtbl, "[]", rb_f_getenv, 1);
rb_define_singleton_method(envtbl, "fetch", env_fetch, -1);
@@ -7511,14 +7671,13 @@ Init_Hash(void)
rb_undef_method(envtbl_class, "initialize_dup");
/*
- * ENV is a Hash-like accessor for environment variables.
+ * +ENV+ is a Hash-like accessor for environment variables.
*
* See ENV (the class) for more details.
*/
rb_define_global_const("ENV", envtbl);
- /* for callcc */
- ruby_register_rollback_func_for_ensure(hash_foreach_ensure, hash_foreach_ensure_rollback);
-
HASH_ASSERT(sizeof(ar_hint_t) * RHASH_AR_TABLE_MAX_SIZE == sizeof(VALUE));
}
+
+#include "hash.rbinc"