summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS5
-rw-r--r--error.c36
-rw-r--r--hash.c4
-rw-r--r--internal.h3
-rw-r--r--sprintf.c2
-rw-r--r--test/ruby/test_env.rb4
-rw-r--r--test/ruby/test_hash.rb6
-rw-r--r--test/ruby/test_sprintf.rb5
8 files changed, 58 insertions, 7 deletions
diff --git a/NEWS b/NEWS
index d4ada857fa..717ae69b65 100644
--- a/NEWS
+++ b/NEWS
@@ -109,6 +109,11 @@ with all sufficient information, see the ChangeLog file or Redmine
* Description set by Thread#name= is now visible on Windows 10.
+* KeyError
+
+ * KeyError#receiver [Feature #12063]
+ * KeyError#key [Feature #12063]
+
=== Stdlib updates (outstanding ones only)
* Bundler
diff --git a/error.c b/error.c
index a12c5d206d..ffdd1a4ef1 100644
--- a/error.c
+++ b/error.c
@@ -815,7 +815,7 @@ VALUE rb_mErrno;
static VALUE rb_eNOERROR;
static ID id_new, id_cause, id_message, id_backtrace;
-static ID id_name, id_args, id_Errno, id_errno, id_i_path;
+static ID id_name, id_key, id_args, id_Errno, id_errno, id_i_path;
static ID id_receiver, id_iseq, id_local_variables;
static ID id_private_call_p;
extern ID ruby_static_id_status;
@@ -1547,6 +1547,37 @@ rb_invalid_str(const char *str, const char *type)
rb_raise(rb_eArgError, "invalid value for %s: %+"PRIsVALUE, type, s);
}
+static VALUE
+key_err_receiver(VALUE self)
+{
+ VALUE recv;
+
+ recv = rb_ivar_lookup(self, id_receiver, Qundef);
+ if (recv != Qundef) return recv;
+ rb_raise(rb_eArgError, "no receiver is available");
+}
+
+static VALUE
+key_err_key(VALUE self)
+{
+ VALUE key;
+
+ key = rb_ivar_lookup(self, id_key, Qundef);
+ if (key != Qundef) return key;
+ rb_raise(rb_eArgError, "no key is available");
+}
+
+VALUE
+rb_key_err_new(VALUE mesg, VALUE recv, VALUE key)
+{
+ VALUE exc = rb_obj_alloc(rb_eKeyError);
+ rb_ivar_set(exc, id_mesg, mesg);
+ rb_ivar_set(exc, id_bt, Qnil);
+ rb_ivar_set(exc, id_key, key);
+ rb_ivar_set(exc, id_receiver, recv);
+ return exc;
+}
+
/*
* call-seq:
* SyntaxError.new([msg]) -> syntax_error
@@ -2161,6 +2192,8 @@ Init_Exception(void)
rb_eArgError = rb_define_class("ArgumentError", rb_eStandardError);
rb_eIndexError = rb_define_class("IndexError", rb_eStandardError);
rb_eKeyError = rb_define_class("KeyError", rb_eIndexError);
+ rb_define_method(rb_eKeyError, "receiver", key_err_receiver, 0);
+ rb_define_method(rb_eKeyError, "key", key_err_key, 0);
rb_eRangeError = rb_define_class("RangeError", rb_eStandardError);
rb_eScriptError = rb_define_class("ScriptError", rb_eException);
@@ -2216,6 +2249,7 @@ Init_Exception(void)
id_message = rb_intern_const("message");
id_backtrace = rb_intern_const("backtrace");
id_name = rb_intern_const("name");
+ id_key = rb_intern_const("key");
id_args = rb_intern_const("args");
id_receiver = rb_intern_const("receiver");
id_private_call_p = rb_intern_const("private_call?");
diff --git a/hash.c b/hash.c
index bc02696b30..7198e8a9d8 100644
--- a/hash.c
+++ b/hash.c
@@ -912,7 +912,7 @@ rb_hash_fetch_m(int argc, VALUE *argv, VALUE hash)
desc = rb_any_to_s(key);
}
desc = rb_str_ellipsize(desc, 65);
- rb_raise(rb_eKeyError, "key not found: %"PRIsVALUE, desc);
+ rb_key_err_raise(rb_sprintf("key not found: %"PRIsVALUE, desc), hash, key);
}
return argv[1];
}
@@ -3375,7 +3375,7 @@ env_fetch(int argc, VALUE *argv)
if (!env) {
if (block_given) return rb_yield(key);
if (argc == 1) {
- rb_raise(rb_eKeyError, "key not found: \"%"PRIsVALUE"\"", key);
+ rb_key_err_raise(rb_sprintf("key not found: \"%"PRIsVALUE"\"", key), envtbl, key);
}
return argv[1];
}
diff --git a/internal.h b/internal.h
index 3e6b30a235..3ff731c2de 100644
--- a/internal.h
+++ b/internal.h
@@ -1156,6 +1156,9 @@ VALUE rb_name_err_new(VALUE mesg, VALUE recv, VALUE method);
rb_exc_raise(rb_name_err_new(mesg, recv, name))
#define rb_name_err_raise(mesg, recv, name) \
rb_name_err_raise_str(rb_fstring_cstr(mesg), (recv), (name))
+VALUE rb_key_err_new(VALUE mesg, VALUE recv, VALUE name);
+#define rb_key_err_raise(mesg, recv, name) \
+ rb_exc_raise(rb_key_err_new(mesg, recv, name))
NORETURN(void ruby_deprecated_internal_feature(const char *));
#define DEPRECATED_INTERNAL_FEATURE(func) \
(ruby_deprecated_internal_feature(func), UNREACHABLE)
diff --git a/sprintf.c b/sprintf.c
index 2bd4966a1b..a956381cd4 100644
--- a/sprintf.c
+++ b/sprintf.c
@@ -633,7 +633,7 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
}
nextvalue = rb_hash_default_value(hash, sym);
if (NIL_P(nextvalue)) {
- rb_enc_raise(enc, rb_eKeyError, "key%.*s not found", len, start);
+ rb_key_err_raise(rb_enc_sprintf(enc, "key%.*s not found", len, start), hash, sym);
}
}
if (term == '}') goto format_s;
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index 29c058339f..d136ba75fa 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -122,9 +122,11 @@ class TestEnv < Test::Unit::TestCase
assert_equal("foo", ENV.fetch("test"))
ENV.delete("test")
feature8649 = '[ruby-core:56062] [Feature #8649]'
- assert_raise_with_message(KeyError, 'key not found: "test"', feature8649) do
+ e = assert_raise_with_message(KeyError, 'key not found: "test"', feature8649) do
ENV.fetch("test")
end
+ assert_same(ENV, e.receiver)
+ assert_equal("test", e.key)
assert_equal("foo", ENV.fetch("test", "foo"))
assert_equal("bar", ENV.fetch("test") { "bar" })
assert_equal("bar", ENV.fetch("test", "foo") { "bar" })
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 8fb1c17003..ebc622a827 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -531,6 +531,8 @@ class TestHash < Test::Unit::TestCase
e = assert_raise(KeyError) { @h.fetch('gumby'*20) }
assert_match(/key not found: "gumbygumby/, e.message)
assert_match(/\.\.\.\z/, e.message)
+ assert_same(@h, e.receiver)
+ assert_equal('gumby'*20, e.key)
end
def test_key2?
@@ -591,9 +593,11 @@ class TestHash < Test::Unit::TestCase
assert_equal(4, res.length)
assert_equal %w( three two one nil ), res
- assert_raise KeyError do
+ e = assert_raise KeyError do
@h.fetch_values(3, 'invalid')
end
+ assert_same(@h, e.receiver)
+ assert_equal('invalid', e.key)
res = @h.fetch_values(3, 'invalid') { |k| k.upcase }
assert_equal %w( three INVALID ), res
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index b46f5189e2..a07ac7908b 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -452,7 +452,10 @@ class TestSprintf < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, "named<key2> after numbered") {sprintf("%1$<key2>s", :key => "value")}
assert_raise_with_message(ArgumentError, "named<key2> after unnumbered(2)") {sprintf("%s%s%<key2>s", "foo", "bar", :key => "value")}
assert_raise_with_message(ArgumentError, "named<key2> after <key>") {sprintf("%<key><key2>s", :key => "value")}
- assert_raise_with_message(KeyError, "key<key> not found") {sprintf("%<key>s", {})}
+ h = {}
+ e = assert_raise_with_message(KeyError, "key<key> not found") {sprintf("%<key>s", h)}
+ assert_same(h, e.receiver)
+ assert_equal(:key, e.key)
end
def test_named_untyped_enc