`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(); 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_data_t key = (st_data_t)rb_intern_str(body->location.label); // String -> ID
st_insert(vm->unused_block_warning_table, key, 1); st_insert(vm->unused_block_warning_table, key, 1);
} }

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

@ -86,6 +86,7 @@ static ID id_category;
static ID id_deprecated; static ID id_deprecated;
static ID id_experimental; static ID id_experimental;
static ID id_performance; static ID id_performance;
static ID id_strict_unused_block;
static VALUE sym_category; static VALUE sym_category;
static VALUE sym_highlight; static VALUE sym_highlight;
static struct { static struct {
@ -3584,6 +3585,7 @@ Init_Exception(void)
id_deprecated = rb_intern_const("deprecated"); id_deprecated = rb_intern_const("deprecated");
id_experimental = rb_intern_const("experimental"); id_experimental = rb_intern_const("experimental");
id_performance = rb_intern_const("performance"); id_performance = rb_intern_const("performance");
id_strict_unused_block = rb_intern_const("strict_unused_block");
id_top = rb_intern_const("top"); id_top = rb_intern_const("top");
id_bottom = rb_intern_const("bottom"); id_bottom = rb_intern_const("bottom");
id_iseq = rb_make_internal_id(); 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_deprecated, RB_WARN_CATEGORY_DEPRECATED);
st_add_direct(warning_categories.id2enum, id_experimental, RB_WARN_CATEGORY_EXPERIMENTAL); 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_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(); 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_NONE, 0);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_DEPRECATED, id_deprecated); 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_EXPERIMENTAL, id_experimental);
st_add_direct(warning_categories.enum2id, RB_WARN_CATEGORY_PERFORMANCE, id_performance); 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 void

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

@ -53,6 +53,9 @@ typedef enum {
/** Warning is for performance issues (not enabled by -w). */ /** Warning is for performance issues (not enabled by -w). */
RB_WARN_CATEGORY_PERFORMANCE, RB_WARN_CATEGORY_PERFORMANCE,
/** Warning is for checking unused block strictly */
RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK,
RB_WARN_CATEGORY_DEFAULT_BITS = ( RB_WARN_CATEGORY_DEFAULT_BITS = (
(1U << RB_WARN_CATEGORY_DEPRECATED) | (1U << RB_WARN_CATEGORY_DEPRECATED) |
(1U << RB_WARN_CATEGORY_EXPERIMENTAL) | (1U << RB_WARN_CATEGORY_EXPERIMENTAL) |
@ -62,6 +65,7 @@ typedef enum {
(1U << RB_WARN_CATEGORY_DEPRECATED) | (1U << RB_WARN_CATEGORY_DEPRECATED) |
(1U << RB_WARN_CATEGORY_EXPERIMENTAL) | (1U << RB_WARN_CATEGORY_EXPERIMENTAL) |
(1U << RB_WARN_CATEGORY_PERFORMANCE) | (1U << RB_WARN_CATEGORY_PERFORMANCE) |
(1U << RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK) |
0) 0)
} rb_warning_category_t; } 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("deprecated", "", "Deprecated features."),
M("experimental", "", "Experimental features."), M("experimental", "", "Experimental features."),
M("performance", "", "Performance issues."), M("performance", "", "Performance issues."),
M("strict_unused_block", "", "Warning unused block strictly"),
}; };
#if USE_RJIT #if USE_RJIT
extern const struct ruby_opt_message rb_rjit_option_messages[]; 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)) { else if (NAME_MATCH_P("performance", s, len)) {
bits = 1U << RB_WARN_CATEGORY_PERFORMANCE; 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 { else {
rb_warn("unknown warning category: '%s'", s); 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(-We) + ['p $-W'], "", %w(2), [])
assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), []) 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 assert_equal categories.keys.sort, Warning.categories.sort
categories.each do |category, level| categories.each do |category, level|

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

@ -4271,12 +4271,6 @@ Init_BareVM(void)
vm->constant_cache = rb_id_table_create(0); vm->constant_cache = rb_id_table_create(0);
vm->unused_block_warning_table = st_init_numtable(); 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 // setup main thread
th->nt = ZALLOC(struct rb_native_thread); th->nt = ZALLOC(struct rb_native_thread);
th->vm = vm; th->vm = vm;

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

@ -799,7 +799,6 @@ typedef struct rb_vm_struct {
struct rb_id_table *negative_cme_table; struct rb_id_table *negative_cme_table;
st_table *overloaded_cme_table; // cme -> overloaded_cme st_table *overloaded_cme_table; // cme -> overloaded_cme
st_table *unused_block_warning_table; 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 // 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 // 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(); rb_vm_t *vm = GET_VM();
st_table *dup_check_table = vm->unused_block_warning_table; st_table *dup_check_table = vm->unused_block_warning_table;
st_data_t key; st_data_t key;
bool strict_unused_block = rb_warning_category_enabled_p(RB_WARN_CATEGORY_STRICT_UNUSED_BLOCK);
union { union {
VALUE v; VALUE v;
@ -3046,7 +3047,7 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq,
}; };
// relax check // relax check
if (!vm->unused_block_warning_strict) { if (!strict_unused_block) {
key = (st_data_t)cme->def->original_id; key = (st_data_t)cme->def->original_id;
if (st_lookup(dup_check_table, key, NULL)) { 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)) { if (st_insert(dup_check_table, key, 1)) {
// already shown // 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 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); VALUE name = rb_gen_method_name(cme->defined_class, ISEQ_BODY(iseq)->location.base_label);
if (!NIL_P(m_loc)) { if (!NIL_P(m_loc)) {
rb_warning("the block passed to '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored", 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)); name, RARRAY_AREF(m_loc, 0), RARRAY_AREF(m_loc, 1));
} }
else { 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);
} }
} }
} }