From f34078b6598b055b43ccb9b0629cf8a3dab68c2b Mon Sep 17 00:00:00 2001
From: Nobuyoshi Nakada <nobu@ruby-lang.org>
Date: Tue, 9 Apr 2013 16:36:16 +0900
Subject: [PATCH] hash.c: Hash#include? improve

* hash.c (rb_hash_include_p, env_include_p): improve so that optional
  argument can match found data.  [ruby-core:54131] [Feature #8229]
---
 hash.c                 | 67 +++++++++++++++++++++++++++++++++++++++++++++++---
 test/ruby/test_env.rb  |  9 +++++++
 test/ruby/test_hash.rb |  3 +++
 3 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/hash.c b/hash.c
index 9a0f71d..cf654e3 100644
--- a/hash.c
+++ b/hash.c
@@ -1599,7 +1599,6 @@ rb_hash_values(VALUE hash)
 /*
  *  call-seq:
  *     hsh.has_key?(key)    -> true or false
- *     hsh.include?(key)    -> true or false
  *     hsh.key?(key)        -> true or false
  *     hsh.member?(key)     -> true or false
  *
@@ -1622,6 +1621,37 @@ rb_hash_has_key(VALUE hash, VALUE key)
     return Qfalse;
 }
 
+/*
+ *  call-seq:
+ *     hsh.include?(key)        -> true or false
+ *     hsh.include?(key, value) -> true or false
+ *
+ *  Returns +true+ if the given _key_ is present in _hsh_,
+ *  and the value equals to the given _value_.
+ *
+ *     h = { "a" => 100, "b" => 200 }
+ *     h.include?("a")       #=> true
+ *     h.include?("z")       #=> false
+ *     h.include?("a", 100)  #=> true
+ *     h.include?("a", 101)  #=> false
+ *
+ */
+
+static VALUE
+rb_hash_include_p(int argc, VALUE *argv, VALUE hash)
+{
+    st_data_t data;
+
+    rb_check_arity(argc, 1, 2);
+    if (!RHASH(hash)->ntbl)
+        return Qfalse;
+    if (st_lookup(RHASH(hash)->ntbl, (st_data_t)argv[0], &data)) {
+	if (argc < 2) return Qtrue;
+	return rb_equal((VALUE)data, argv[1]);
+    }
+    return Qfalse;
+}
+
 static int
 rb_hash_search_value(VALUE key, VALUE value, VALUE arg)
 {
@@ -3008,7 +3038,6 @@ env_empty_p(void)
 /*
  * call-seq:
  *   ENV.key?(name)     -> true or false
- *   ENV.include?(name) -> true or false
  *   ENV.has_key?(name) -> true or false
  *   ENV.member?(name)  -> true or false
  *
@@ -3029,6 +3058,36 @@ env_has_key(VALUE env, VALUE key)
 
 /*
  * call-seq:
+ *   ENV.include?(name)        -> true or false
+ *   ENV.include?(name, value) -> true or false
+ *
+ * Returns +true+ if there is an environment variable with the given +name+,
+ * and the value equals to the given _value_.
+ */
+static VALUE
+env_include_p(int argc, VALUE *argv, VALUE env)
+{
+    const char *s, *v, *e;
+    VALUE key, value;
+
+    rb_check_arity(argc, 1, 2);
+    rb_secure(4);
+    key = argv[0];
+    s = StringValuePtr(key);
+    if (memchr(s, '\0', RSTRING_LEN(key)))
+	rb_raise(rb_eArgError, "bad environment variable name");
+    e = getenv(s);
+    if (argc > 1) {
+	value = argv[1];
+	if (NIL_P(value)) return e ? Qfalse : Qtrue;
+	v = StringValueCStr(value);
+	return (!e || strcmp(v, e)) ? Qfalse : Qtrue;
+    }
+    return e ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
  *   ENV.assoc(name) -> Array or nil
  *
  * Returns an Array of the name and value of the environment variable with
@@ -3487,7 +3546,7 @@ Init_Hash(void)
     rb_define_method(rb_cHash, "rassoc", rb_hash_rassoc, 1);
     rb_define_method(rb_cHash, "flatten", rb_hash_flatten, -1);
 
-    rb_define_method(rb_cHash,"include?", rb_hash_has_key, 1);
+    rb_define_method(rb_cHash,"include?", rb_hash_include_p, -1);
     rb_define_method(rb_cHash,"member?", rb_hash_has_key, 1);
     rb_define_method(rb_cHash,"has_key?", rb_hash_has_key, 1);
     rb_define_method(rb_cHash,"has_value?", rb_hash_has_value, 1);
@@ -3542,7 +3601,7 @@ Init_Hash(void)
     rb_define_singleton_method(envtbl,"keys", env_keys, 0);
     rb_define_singleton_method(envtbl,"values", env_values, 0);
     rb_define_singleton_method(envtbl,"values_at", env_values_at, -1);
-    rb_define_singleton_method(envtbl,"include?", env_has_key, 1);
+    rb_define_singleton_method(envtbl,"include?", env_include_p, -1);
     rb_define_singleton_method(envtbl,"member?", env_has_key, 1);
     rb_define_singleton_method(envtbl,"has_key?", env_has_key, 1);
     rb_define_singleton_method(envtbl,"has_value?", env_has_value, 1);
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index 618be37..2423c40 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -394,6 +394,15 @@ class TestEnv < Test::Unit::TestCase
     end
   end
 
+  def test_include_p
+    feature8229 = '[ruby-core:54131] [Feature #8229]'
+    assert_not_send([ENV, :include?, "test"])
+    ENV["test"] = "foo"
+    assert_send([ENV, :include?, "test"])
+    assert_send([ENV, :include?, "test", "foo"], feature8229)
+    assert_not_send([ENV, :include?, "test", "bar"], feature8229)
+  end
+
   if /mswin|mingw/ =~ RUBY_PLATFORM
     def test_win32_blocksize
       len = 32767 - ENV.to_a.flatten.inject(0) {|r,e| r + e.size + 2 }
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 4aee4a0..92d8f8e 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -435,11 +435,14 @@ class TestHash < Test::Unit::TestCase
   end
 
   def test_include?
+    feature8229 = '[ruby-core:54131] [Feature #8229]'
     assert_not_send([@cls[], :include?, 1])
     assert_not_send([@cls[], :include?, nil])
     assert_send([@h, :include?, nil])
     assert_send([@h, :include?, 1])
     assert_not_send([@h, :include?, 'gumby'])
+    assert_send([@h, :include?, 1, 'one'], feature8229)
+    assert_not_send([@h, :include?, 1, 'uno'], feature8229)
   end
 
   def test_key
-- 
1.8.2

