Add rb_category_warn{,ing} for warning messages with categories

This adds the following C-API functions that can be used to emit
warnings with categories included:

```c
void rb_category_warn(const char *, const char*, ...)
void rb_category_warning(const char*, const char*, ...)
```

Internally in error.c, there is an rb_warn_category function
that will call Warning.warn with the string and the category
keyword if it doesn't have an arity of 1, and will call
Warning.warn with just the string if it has an arity of 1.
This refactors the rb_warn_deprecated{,_to_remove} functions
to use rb_warn_category.

This makes Kernel#warn accept a category keyword and pass it
to Warning.warn, so that Ruby methods can more easily emit
warnings with categories.  rb_warn_category makes sure that
the passed category is a already defined category symbol
before calling Warning.warn.

The only currently defined warning category is :deprecated,
since that is what is already used.  More categories can be
added in later commits.
This commit is contained in:
Jeremy Evans 2020-09-03 08:00:10 -07:00
Родитель 92c3ad9c27
Коммит 346301e232
5 изменённых файлов: 135 добавлений и 34 удалений

86
error.c
Просмотреть файл

@ -73,6 +73,8 @@ static VALUE rb_cWarningBuffer;
static ID id_warn;
static ID id_category;
static VALUE sym_category;
static VALUE warning_categories;
extern const char ruby_description[];
@ -276,6 +278,34 @@ rb_warning_warn(VALUE mod, VALUE str)
return rb_funcallv(mod, id_warn, 1, &str);
}
static int
rb_warning_warn_arity(void) {
return rb_method_entry_arity(rb_method_entry(rb_singleton_class(rb_mWarning), id_warn));
}
static VALUE
rb_warn_category(VALUE str, VALUE category)
{
if (category != Qnil) {
category = rb_to_symbol_type(category);
if (rb_hash_aref(warning_categories, category) != Qtrue) {
rb_raise(rb_eArgError, "invalid warning category used: %s", rb_id2name(SYM2ID(category)));
}
}
if (rb_warning_warn_arity() == 1) {
return rb_warning_warn(rb_mWarning, str);
}
else {
VALUE args[2];
args[0] = str;
args[1] = rb_hash_new();
rb_hash_aset(args[1], sym_category, category);
return rb_funcallv_kw(rb_mWarning, id_warn, 2, args, RB_PASS_KEYWORDS);
}
}
static void
rb_write_warning_str(VALUE str)
{
@ -344,6 +374,16 @@ rb_warn(const char *fmt, ...)
}
}
void
rb_category_warn(const char *category, const char *fmt, ...)
{
if (!NIL_P(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
rb_warn_category(mesg, ID2SYM(rb_intern(category)));
}
}
}
void
rb_enc_warn(rb_encoding *enc, const char *fmt, ...)
{
@ -365,6 +405,17 @@ rb_warning(const char *fmt, ...)
}
}
/* rb_category_warning() reports only in verbose mode */
void
rb_category_warning(const char *category, const char *fmt, ...)
{
if (RTEST(ruby_verbose)) {
with_warning_string(mesg, 0, fmt) {
rb_warn_category(mesg, ID2SYM(rb_intern(category)));
}
}
}
VALUE
rb_warning_string(const char *fmt, ...)
{
@ -385,8 +436,6 @@ rb_enc_warning(rb_encoding *enc, const char *fmt, ...)
}
#endif
static void warn_deprecated(VALUE mesg);
void
rb_warn_deprecated(const char *fmt, const char *suggest, ...)
{
@ -400,7 +449,7 @@ rb_warn_deprecated(const char *fmt, const char *suggest, ...)
rb_str_cat_cstr(mesg, " is deprecated");
if (suggest) rb_str_catf(mesg, "; use %s instead", suggest);
rb_str_cat_cstr(mesg, "\n");
warn_deprecated(mesg);
rb_warn_category(mesg, ID2SYM(rb_intern("deprecated")));
}
void
@ -414,24 +463,7 @@ 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);
warn_deprecated(mesg);
}
static void
warn_deprecated(VALUE mesg)
{
VALUE warn_args[2] = {mesg};
int kwd = 0;
const rb_method_entry_t *me = rb_method_entry(rb_singleton_class(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;
kwd = 1;
}
rb_funcallv_kw(rb_mWarning, id_warn, 1 + kwd, warn_args, kwd);
rb_warn_category(mesg, ID2SYM(rb_intern("deprecated")));
}
static inline int
@ -453,7 +485,7 @@ warning_write(int argc, VALUE *argv, VALUE buf)
VALUE rb_ec_backtrace_location_ary(rb_execution_context_t *ec, long lev, long n);
static VALUE
rb_warn_m(rb_execution_context_t *ec, VALUE exc, VALUE msgs, VALUE uplevel)
rb_warn_m(rb_execution_context_t *ec, VALUE exc, VALUE msgs, VALUE uplevel, VALUE category)
{
VALUE location = Qnil;
int argc = RARRAY_LENINT(msgs);
@ -489,12 +521,13 @@ rb_warn_m(rb_execution_context_t *ec, VALUE exc, VALUE msgs, VALUE uplevel)
rb_io_puts(argc, argv, str);
RBASIC_SET_CLASS(str, rb_cString);
}
if (exc == rb_mWarning) {
rb_must_asciicompat(str);
rb_write_error_str(str);
}
else {
rb_write_warning_str(str);
rb_warn_category(str, category);
}
}
return Qnil;
@ -2758,6 +2791,13 @@ Init_Exception(void)
id_bottom = rb_intern_const("bottom");
id_iseq = rb_make_internal_id();
id_recv = rb_make_internal_id();
sym_category = ID2SYM(id_category);
warning_categories = rb_hash_new();
rb_gc_register_mark_object(warning_categories);
rb_hash_aset(warning_categories, ID2SYM(rb_intern("deprecated")), Qtrue);
rb_obj_freeze(warning_categories);
}
void

Просмотреть файл

@ -63,10 +63,12 @@ VALUE *rb_ruby_debug_ptr(void);
/* reports if `-W' specified */
PRINTF_ARGS(void rb_warning(const char*, ...), 1, 2);
PRINTF_ARGS(void rb_category_warning(const char*, const char*, ...), 2, 3);
PRINTF_ARGS(void rb_compile_warning(const char *, int, const char*, ...), 3, 4);
PRINTF_ARGS(void rb_sys_warning(const char*, ...), 1, 2);
/* reports always */
COLDFUNC PRINTF_ARGS(void rb_warn(const char*, ...), 1, 2);
COLDFUNC PRINTF_ARGS(void rb_category_warn(const char *, const char*, ...), 2, 3);
PRINTF_ARGS(void rb_compile_warn(const char *, int, const char*, ...), 3, 4);
RBIMPL_SYMBOL_EXPORT_END()

Просмотреть файл

@ -17,7 +17,7 @@ describe "Kernel#warn" do
Kernel.should have_private_instance_method(:warn)
end
it "requires multiple arguments" do
it "accepts multiple arguments" do
Kernel.method(:warn).arity.should < 0
end
@ -114,6 +114,38 @@ describe "Kernel#warn" do
end
end
ruby_version_is "3.0" do
it "accepts :category keyword with a symbol" do
-> {
$VERBOSE = true
warn("message", category: :deprecated)
}.should output(nil, "message\n")
end
it "accepts :category keyword with nil" do
-> {
$VERBOSE = true
warn("message", category: nil)
}.should output(nil, "message\n")
end
it "accepts :category keyword with object convertible to symbol" do
o = Object.new
def o.to_sym; :deprecated; end
-> {
$VERBOSE = true
warn("message", category: o)
}.should output(nil, "message\n")
end
it "raises if :category keyword is not nil and not convertible to symbol" do
-> {
$VERBOSE = true
warn("message", category: Object.new)
}.should raise_error(TypeError)
end
end
it "converts first arg using to_s" do
w = KernelSpecs::WarnInNestedCall.new

Просмотреть файл

@ -51,14 +51,41 @@ describe "Warning.warn" do
end
end
it "is called by Kernel.warn" do
Warning.should_receive(:warn).with("Chunky bacon!\n")
verbose = $VERBOSE
$VERBOSE = false
begin
Kernel.warn("Chunky bacon!")
ensure
$VERBOSE = verbose
ruby_version_is '3.0' do
it "is called by Kernel.warn with nil category keyword" do
Warning.should_receive(:warn).with("Chunky bacon!\n", category: nil)
verbose = $VERBOSE
$VERBOSE = false
begin
Kernel.warn("Chunky bacon!")
ensure
$VERBOSE = verbose
end
end
it "is called by Kernel.warn with given category keyword converted to a symbol" do
Warning.should_receive(:warn).with("Chunky bacon!\n", category: :deprecated)
verbose = $VERBOSE
$VERBOSE = false
begin
Kernel.warn("Chunky bacon!", category: 'deprecated')
ensure
$VERBOSE = verbose
end
end
end
ruby_version_is ''...'3.0' do
it "is called by Kernel.warn" do
Warning.should_receive(:warn).with("Chunky bacon!\n")
verbose = $VERBOSE
$VERBOSE = false
begin
Kernel.warn("Chunky bacon!")
ensure
$VERBOSE = verbose
end
end
end
end

Просмотреть файл

@ -39,7 +39,7 @@ module Kernel
#
# baz.rb:6: warning: invalid call to foo
#
def warn(*msgs, uplevel: nil)
Primitive.rb_warn_m(msgs, uplevel)
def warn(*msgs, uplevel: nil, category: nil)
Primitive.rb_warn_m(msgs, uplevel, category)
end
end