Support passing a category to Warning.warn

This change adds a `category` kwarg to make it easier to monkey patch
`Warning.warn`. Warnings already have a category, but that warning isn't
exposed. This implements a way to get the category so that warnings with
a specific category, like deprecated, can be treated differently than
other warnings in an application.

The change here does an arity check on the method to support backwards
compatibility for applications that may already have a warning monkey
patch.

For our usecase we want to `raise` for deprecation warnings in order to
get the behavior for the next Ruby version. For example, now that we
fixed all our warnings and deployed Ruby 2.7 to production, we want to
be able to have deprecation warnings behave like they would in 3.0: raise
an error. For other warnings, like uninialized constants, that behavior
won't be removed from Ruby in the next version, so we don't need to
raise errors.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
This commit is contained in:
eileencodes 2020-08-06 13:25:11 -04:00 committed by Jeremy Evans
parent de10a1f358
commit 6e8ec9ab6d
Notes: git 2020-09-02 08:16:38 +09:00
3 changed files with 54 additions and 8 deletions

View File

@ -165,6 +165,13 @@ Outstanding ones only.
* Symbol#to_proc now returns a lambda Proc.
[[Feature #16260]]
* Warning
* Modified method
* Warning#warn now supports a category kwarg.
[[Feature #17122]]
## Stdlib updates
Outstanding ones only.
@ -342,3 +349,4 @@ Excluding feature bug fixes.
[Feature #16686]: https://bugs.ruby-lang.org/issues/16686
[Misc #16961]: https://bugs.ruby-lang.org/issues/16961
[Bug #8446]: https://bugs.ruby-lang.org/issues/8446
[Feature #17122]: https://bugs.ruby-lang.org/issues/17122

30
error.c
View File

@ -229,15 +229,20 @@ rb_warning_s_aset(VALUE mod, VALUE category, VALUE flag)
/*
* call-seq:
* warn(msg) -> nil
* warn(msg, **kw) -> nil
*
* Writes warning message +msg+ to $stderr. This method is called by
* Ruby for all emitted warnings.
* Ruby for all emitted warnings. A +category+ may be included with
* the warning.
*/
static VALUE
rb_warning_s_warn(VALUE mod, VALUE str)
rb_warning_s_warn(int argc, VALUE *argv, VALUE mod)
{
VALUE str;
VALUE opt;
rb_scan_args(argc, argv, "1:", &str, &opt);
Check_Type(str, T_STRING);
rb_must_asciicompat(str);
rb_write_error_str(str);
@ -401,7 +406,22 @@ rb_warn_deprecated_to_remove(const char *fmt, const char *removal, ...)
va_end(args);
rb_str_set_len(mesg, RSTRING_LEN(mesg) - 1);
rb_str_catf(mesg, " is deprecated and will be removed in Ruby %s\n", removal);
rb_write_warning_str(mesg);
VALUE warn_args[2];
warn_args[0] = mesg;
const rb_method_entry_t * me;
me = rb_method_entry(rb_mWarning, id_warn);
if (rb_method_entry_arity(me) != 1) {
VALUE kwargs = rb_hash_new();
rb_hash_aset(kwargs, ID2SYM(rb_intern("category")), ID2SYM(rb_intern("deprecated")));
warn_args[1] = kwargs;
rb_funcallv_kw(rb_mWarning, id_warn, 2, warn_args, RB_PASS_KEYWORDS);
} else {
rb_funcall(rb_mWarning, id_warn, 1, mesg);
}
}
static inline int
@ -2658,7 +2678,7 @@ Init_Exception(void)
rb_mWarning = rb_define_module("Warning");
rb_define_singleton_method(rb_mWarning, "[]", rb_warning_s_aref, 1);
rb_define_singleton_method(rb_mWarning, "[]=", rb_warning_s_aset, 2);
rb_define_method(rb_mWarning, "warn", rb_warning_s_warn, 1);
rb_define_method(rb_mWarning, "warn", rb_warning_s_warn, -1);
rb_extend_object(rb_mWarning, rb_mWarning);
/* :nodoc: */

View File

@ -915,7 +915,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
end
def capture_warning_warn
def capture_warning_warn(category: false)
verbose = $VERBOSE
deprecated = Warning[:deprecated]
warning = []
@ -924,8 +924,14 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
alias_method :warn2, :warn
remove_method :warn
define_method(:warn) do |str|
warning << str
if category
define_method(:warn) do |str, **kw|
warning << [str, kw[:category]]
end
else
define_method(:warn) do |str|
warning << str
end
end
end
@ -954,6 +960,18 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_equal(["\n"], capture_warning_warn {warn ""})
end
def test_warn_backwards_compatibility
warning = capture_warning_warn { Object.new.tainted? }
assert_match(/deprecated/, warning[0])
end
def test_warn_category
warning = capture_warning_warn(category: true) { Object.new.tainted? }
assert_equal :deprecated, warning[0][1]
end
def test_kernel_warn_uplevel
warning = capture_warning_warn {warn("test warning", uplevel: 0)}
assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0])