[前][次][番号順一覧][スレッド一覧]

ruby-changes:63186

From: Jeremy <ko1@a...>
Date: Tue, 29 Sep 2020 00:38:23 +0900 (JST)
Subject: [ruby-changes:63186] 346301e232 (master): Add rb_category_warn{, ing} for warning messages with categories

https://git.ruby-lang.org/ruby.git/commit/?id=346301e232

From 346301e2329c46362a6089311d0a64b8734b35ec Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Thu, 3 Sep 2020 08:00:10 -0700
Subject: 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.

diff --git a/error.c b/error.c
index e064eef..fff9b4a 100644
--- a/error.c
+++ b/error.c
@@ -73,6 +73,8 @@ static VALUE rb_cWarningBuffer; https://github.com/ruby/ruby/blob/trunk/error.c#L73
 
 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) https://github.com/ruby/ruby/blob/trunk/error.c#L278
     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)
 {
@@ -345,6 +375,16 @@ rb_warn(const char *fmt, ...) https://github.com/ruby/ruby/blob/trunk/error.c#L375
 }
 
 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, ...)
 {
     if (!NIL_P(ruby_verbose)) {
@@ -365,6 +405,17 @@ rb_warning(const char *fmt, ...) https://github.com/ruby/ruby/blob/trunk/error.c#L405
     }
 }
 
+/* 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, ...) https://github.com/ruby/ruby/blob/trunk/error.c#L436
 }
 #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, ...) https://github.com/ruby/ruby/blob/trunk/error.c#L449
     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, ...) https://github.com/ruby/ruby/blob/trunk/error.c#L463
     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) https://github.com/ruby/ruby/blob/trunk/error.c#L485
 
 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) https://github.com/ruby/ruby/blob/trunk/error.c#L521
 	    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) https://github.com/ruby/ruby/blob/trunk/error.c#L2791
     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
diff --git a/include/ruby/internal/error.h b/include/ruby/internal/error.h
index 98f016d..dc842cc 100644
--- a/include/ruby/internal/error.h
+++ b/include/ruby/internal/error.h
@@ -63,10 +63,12 @@ VALUE *rb_ruby_debug_ptr(void); https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/error.h#L63
 
 /* 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()
diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb
index 79e78dd..de08cd8 100644
--- a/spec/ruby/core/kernel/warn_spec.rb
+++ b/spec/ruby/core/kernel/warn_spec.rb
@@ -17,7 +17,7 @@ describe "Kernel#warn" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/kernel/warn_spec.rb#L17
     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 https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/kernel/warn_spec.rb#L114
       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
 
diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb
index 21424c6..2ded6a1 100644
--- a/spec/ruby/core/warning/warn_spec.rb
+++ b/spec/ruby/core/warning/warn_spec.rb
@@ -51,14 +51,41 @@ describe "Warning.warn" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/warning/warn_spec.rb#L51
     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_r (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]