`Warning[:strict_unused_block]`

to show unused block warning strictly.

```ruby
class C
  def f = nil
end

class D
  def f = yield
end

[C.new, D.new].each{|obj| obj.f{}}
```

In this case, `D#f` accepts a block. However `C#f` doesn't
accept a block. There are some cases passing a block with
`obj.f{}` where `obj` is `C` or `D`. To avoid warnings on
such cases, "unused block warning" will be warned only if
there is not same name which accepts a block.
On the above example, `C.new.f{}` doesn't show any warnings
because there is a same name `D#f` which accepts a block.

We call this default behavior as "relax mode".

`strict_unused_block` new warning category changes from
"relax mode" to "strict mode", we don't check same name
methods and `C.new.f{}` will be warned.

[Feature #15554]
This commit is contained in:
Koichi Sasada 2024-11-06 03:41:59 +09:00
Родитель 4203c70dfa
Коммит ab7ab9e450
8 изменённых файлов: 20 добавлений и 14 удалений

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

@ -2007,7 +2007,7 @@ iseq_set_use_block(rb_iseq_t *iseq)
rb_vm_t *vm = GET_VM();
if (!vm->unused_block_warning_strict) {
if (!rb_warning_category_enabled_p(RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK)) {
st_data_t key = (st_data_t)rb_intern_str(body->location.label); // String -> ID
st_insert(vm->unused_block_warning_table, key, 1);
}

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

@ -86,6 +86,7 @@ static ID id_category;
static ID id_deprecated;
static ID id_experimental;
static ID id_performance;
static ID id_strict_unused_block;
static VALUE sym_category;
static VALUE sym_highlight;
static struct {
@ -3584,6 +3585,7 @@ Init_Exception(void)
id_deprecated = rb_intern_const("deprecated");
id_experimental = rb_intern_const("experimental");
id_performance = rb_intern_const("performance");
id_strict_unused_block = rb_intern_const("strict_unused_block");
id_top = rb_intern_const("top");
id_bottom = rb_intern_const("bottom");
id_iseq = rb_make_internal_id();
@ -3596,12 +3598,14 @@ Init_Exception(void)
st_add_direct(warning_categories.id2enum, id_deprecated, RB_WARN_CATEGORY_DEPRECATED);
st_add_direct(warning_categories.id2enum, id_experimental, RB_WARN_CATEGORY_EXPERIMENTAL);
st_add_direct(warning_categories.id2enum, id_performance, RB_WARN_CATEGORY_PERFORMANCE);
st_add_direct(warning_categories.id2enum, id_strict_unused_block, RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK);
warning_categories.enum2id = rb_init_identtable();
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_NONE, 0);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_DEPRECATED, id_deprecated);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_EXPERIMENTAL, id_experimental);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_PERFORMANCE, id_performance);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK, id_strict_unused_block);
}
void

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

@ -53,6 +53,9 @@ typedef enum {
/** Warning is for performance issues (not enabled by -w). */
RB_WARN_CATEGORY_PERFORMANCE,
/** Warning is for checking unused block strictly */
RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK,
RB_WARN_CATEGORY_DEFAULT_BITS = (
(1U << RB_WARN_CATEGORY_DEPRECATED) |
(1U << RB_WARN_CATEGORY_EXPERIMENTAL) |
@ -62,6 +65,7 @@ typedef enum {
(1U << RB_WARN_CATEGORY_DEPRECATED) |
(1U << RB_WARN_CATEGORY_EXPERIMENTAL) |
(1U << RB_WARN_CATEGORY_PERFORMANCE) |
(1U << RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK) |
0)
} rb_warning_category_t;

4
ruby.c
Просмотреть файл

@ -398,6 +398,7 @@ usage(const char *name, int help, int highlight, int columns)
M("deprecated", "", "Deprecated features."),
M("experimental", "", "Experimental features."),
M("performance", "", "Performance issues."),
M("strict_unused_block", "", "Warning unused block strictly"),
};
#if USE_RJIT
extern const struct ruby_opt_message rb_rjit_option_messages[];
@ -1233,6 +1234,9 @@ proc_W_option(ruby_cmdline_options_t *opt, const char *s, int *warning)
else if (NAME_MATCH_P("performance", s, len)) {
bits = 1U << RB_WARN_CATEGORY_PERFORMANCE;
}
else if (NAME_MATCH_P("strict_unused_block", s, len)) {
bits = 1U << RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK;
}
else {
rb_warn("unknown warning category: '%s'", s);
}

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

@ -118,7 +118,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(-We) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), [])
categories = {deprecated: 1, experimental: 0, performance: 2}
categories = {deprecated: 1, experimental: 0, performance: 2, strict_unused_block: 3}
assert_equal categories.keys.sort, Warning.categories.sort
categories.each do |category, level|

6
vm.c
Просмотреть файл

@ -4271,12 +4271,6 @@ Init_BareVM(void)
vm->constant_cache = rb_id_table_create(0);
vm->unused_block_warning_table = st_init_numtable();
// TODO: remove before Ruby 3.4.0 release
const char *s = getenv("RUBY_TRY_UNUSED_BLOCK_WARNING_STRICT");
if (s && strcmp(s, "1") == 0) {
vm->unused_block_warning_strict = true;
}
// setup main thread
th->nt = ZALLOC(struct rb_native_thread);
th->vm = vm;

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

@ -799,7 +799,6 @@ typedef struct rb_vm_struct {
struct rb_id_table *negative_cme_table;
st_table *overloaded_cme_table; // cme -> overloaded_cme
st_table *unused_block_warning_table;
bool unused_block_warning_strict;
// This id table contains a mapping from ID to ICs. It does this with ID
// keys and nested st_tables as values. The nested tables have ICs as keys

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

@ -3035,6 +3035,7 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq,
rb_vm_t *vm = GET_VM();
st_table *dup_check_table = vm->unused_block_warning_table;
st_data_t key;
bool strict_unused_block = rb_warning_category_enabled_p(RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK);
union {
VALUE v;
@ -3046,7 +3047,7 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq,
};
// relax check
if (!vm->unused_block_warning_strict) {
if (!strict_unused_block) {
key = (st_data_t)cme->def->original_id;
if (st_lookup(dup_check_table, key, NULL)) {
@ -3072,16 +3073,16 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq,
if (st_insert(dup_check_table, key, 1)) {
// already shown
}
else {
else if (RTEST(ruby_verbose) || strict_unused_block) {
VALUE m_loc = rb_method_entry_location((const rb_method_entry_t *)cme);
VALUE name = rb_gen_method_name(cme->defined_class, ISEQ_BODY(iseq)->location.base_label);
if (!NIL_P(m_loc)) {
rb_warning("the block passed to '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored",
name, RARRAY_AREF(m_loc, 0), RARRAY_AREF(m_loc, 1));
rb_warn("the block passed to '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored",
name, RARRAY_AREF(m_loc, 0), RARRAY_AREF(m_loc, 1));
}
else {
rb_warning("the block may be ignored because '%"PRIsVALUE"' does not use a block", name);
rb_warn("the block may be ignored because '%"PRIsVALUE"' does not use a block", name);
}
}
}