From f2ac26d914d349f9fe8fc61ad2db875f03a07fa6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 5 Apr 2024 17:28:45 -0400 Subject: [PATCH 001/135] [ruby/prism] Bump to v0.25.0 https://github.com/ruby/prism/commit/4da514456f --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index d43fcfd36b219b..6cf28460c2d5af 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "0.24.0" + spec.version = "0.25.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index 13a9aabde3e5c3..59a3f0232ac9e6 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "0.24.0" +#define EXPECTED_PRISM_VERSION "0.25.0" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index ac7ab4fff3c65d..36d5d5432df696 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 24 + MINOR_VERSION = 25 # The patch version of prism that we are expecting to find in the serialized # strings. diff --git a/prism/version.h b/prism/version.h index 237796815f95a7..c96fe6882f16a7 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 24 +#define PRISM_VERSION_MINOR 25 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "0.24.0" +#define PRISM_VERSION "0.25.0" #endif From c6c75f33991be7f46f2397723822b62308429e5f Mon Sep 17 00:00:00 2001 From: git Date: Fri, 5 Apr 2024 21:44:54 +0000 Subject: [PATCH 002/135] Update default gems list at f2ac26d914d349f9fe8fc61ad2db87 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e8c80d295ea8ce..8ba955d48e90d4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -45,7 +45,7 @@ The following default gems are updated. * irb 1.12.0 * json 2.7.2 * net-http 0.4.1 -* prism 0.24.0 +* prism 0.25.0 * rdoc 6.6.3.1 * reline 0.5.0 * resolv 0.4.0 From dae503874d3aa054c4e7ae86e03d1338ab40ddc2 Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Sat, 6 Apr 2024 01:18:08 +0900 Subject: [PATCH 003/135] Remove unused functions from `struct rb_parser_config_struct` --- ruby_parser.c | 23 ----------------------- rubyparser.h | 5 ----- universal_parser.c | 8 -------- 3 files changed, 36 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index eabf2cf3e0fefe..b3617d5b5b3025 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -297,12 +297,6 @@ rbool(VALUE v) return RBOOL(v); } -static int -undef_p(VALUE v) -{ - return RB_UNDEF_P(v); -} - static int rtest(VALUE obj) { @@ -357,18 +351,6 @@ rb_errno_ptr2(void) return rb_errno_ptr(); } -static int -fixnum_p(VALUE obj) -{ - return (int)RB_FIXNUM_P(obj); -} - -static int -symbol_p(VALUE obj) -{ - return (int)RB_SYMBOL_P(obj); -} - static void * zalloc(size_t elemsiz) { @@ -447,9 +429,6 @@ static const rb_parser_config_t rb_global_parser_config = { .compile_callback = rb_suppress_tracing, .reg_named_capture_assign = reg_named_capture_assign, - .fixnum_p = fixnum_p, - .symbol_p = symbol_p, - .attr_get = rb_attr_get, .ary_new = rb_ary_new, @@ -457,7 +436,6 @@ static const rb_parser_config_t rb_global_parser_config = { .ary_new_from_args = rb_ary_new_from_args, .ary_unshift = rb_ary_unshift, .ary_new2 = rb_ary_new2, - .ary_clear = rb_ary_clear, .ary_modify = rb_ary_modify, .array_len = rb_array_len, .array_aref = RARRAY_AREF, @@ -586,7 +564,6 @@ static const rb_parser_config_t rb_global_parser_config = { .strtod = ruby_strtod, .rbool = rbool, - .undef_p = undef_p, .rtest = rtest, .nil_p = nil_p, .qnil = Qnil, diff --git a/rubyparser.h b/rubyparser.h index 2b47d08490355f..1705097f7ad3c8 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1248,9 +1248,6 @@ typedef struct rb_parser_config_struct { VALUE (*compile_callback)(VALUE (*func)(VALUE), VALUE arg); NODE *(*reg_named_capture_assign)(struct parser_params* p, VALUE regexp, const rb_code_location_t *loc); - int (*fixnum_p)(VALUE); - int (*symbol_p)(VALUE); - /* Variable */ VALUE (*attr_get)(VALUE obj, ID id); @@ -1260,7 +1257,6 @@ typedef struct rb_parser_config_struct { VALUE (*ary_new_from_args)(long n, ...); VALUE (*ary_unshift)(VALUE ary, VALUE item); VALUE (*ary_new2)(long capa); // ary_new_capa - VALUE (*ary_clear)(VALUE ary); void (*ary_modify)(VALUE ary); long (*array_len)(VALUE a); VALUE (*array_aref)(VALUE, long); @@ -1409,7 +1405,6 @@ typedef struct rb_parser_config_struct { /* Misc */ VALUE (*rbool)(VALUE); - int (*undef_p)(VALUE); int (*rtest)(VALUE obj); int (*nil_p)(VALUE obj); VALUE qnil; diff --git a/universal_parser.c b/universal_parser.c index 1a9619bf31a1c9..80a6f5d14beac3 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -88,11 +88,6 @@ #define compile_callback p->config->compile_callback #define reg_named_capture_assign p->config->reg_named_capture_assign -#undef FIXNUM_P -#define FIXNUM_P p->config->fixnum_p -#undef SYMBOL_P -#define SYMBOL_P p->config->symbol_p - #define rb_attr_get p->config->attr_get #define rb_ary_new p->config->ary_new @@ -102,7 +97,6 @@ #define rb_ary_unshift p->config->ary_unshift #undef rb_ary_new2 #define rb_ary_new2 p->config->ary_new2 -#define rb_ary_clear p->config->ary_clear #define rb_ary_modify p->config->ary_modify #undef RARRAY_LEN #define RARRAY_LEN p->config->array_len @@ -245,8 +239,6 @@ #undef RBOOL #define RBOOL p->config->rbool -#undef UNDEF_P -#define UNDEF_P p->config->undef_p #undef RTEST #define RTEST p->config->rtest #undef NIL_P From f022a700bf57105fca03b537284ed7bf77e8fff7 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Apr 2024 09:39:45 +0900 Subject: [PATCH 004/135] Remove imemo type check for NODE In the past, `rb_iseq_compile_node` received `NODE *` and `struct vm_ifunc *` as `node`. But after e743a35, the function only receives `NODE *`. This commit removes imemo type check to reduce the dependence on `VALUE flags` of `struct RNode`. --- compile.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compile.c b/compile.c index 852825c82b1c5b..dfdbb8f6445a9b 100644 --- a/compile.c +++ b/compile.c @@ -869,10 +869,6 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) DECL_ANCHOR(ret); INIT_ANCHOR(ret); - if (IMEMO_TYPE_P(node, imemo_ifunc)) { - rb_raise(rb_eArgError, "unexpected imemo_ifunc"); - } - if (node == 0) { NO_CHECK(COMPILE(ret, "nil", node)); iseq_set_local_table(iseq, 0); From c2d02a6ac234a1b0fe017196c047bda2034f0684 Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Sat, 6 Apr 2024 18:24:47 +0900 Subject: [PATCH 005/135] [ruby/reline] Continue processing even if terminfo database couldn't be found (https://github.com/ruby/reline/pull/673) Fix https://github.com/ruby/reline/issues/447 https://github.com/ruby/reline/issues/543 This problem occurs when Fiddle can be loaded, curses can be loaded, and TERM is not registered in Terminfo. It should also occur at hardcopy terminals and when Terminfo information is low, but no such reports have been received. Reline should not abort the process because of missing Terminfo. Reline proceeds with `Reline::Terminfo.enabled? == false` when fiddle or curses cannot be loaded. And does the same when Terminfo is present but TERM is not. https://github.com/ruby/reline/blob/ebab2875f1226f877376558d8758bc0e2a1776c7/lib/reline/terminfo.rb#L156-L160 You can check the operation with `TERM=foo bundle exec bin/console`. https://github.com/ruby/reline/commit/4ce247ce2b --- lib/reline/ansi.rb | 4 ++-- lib/reline/terminfo.rb | 21 +++++++-------------- test/reline/test_ansi_with_terminfo.rb | 2 +- test/reline/test_terminfo.rb | 2 +- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index 0d7226b36f41e0..a3719f502c957a 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -315,7 +315,7 @@ def self.move_cursor_down(x) end def self.hide_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('civis') rescue Reline::Terminfo::TerminfoError @@ -327,7 +327,7 @@ def self.hide_cursor end def self.show_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('cnorm') rescue Reline::Terminfo::TerminfoError diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb index 2cfa32b9f7a9e0..6885a0c6be9242 100644 --- a/lib/reline/terminfo.rb +++ b/lib/reline/terminfo.rb @@ -80,23 +80,11 @@ module Reline::Terminfo def self.setupterm(term, fildes) errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT) ret = @setupterm.(term, fildes, errret_int) - errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i') case ret when 0 # OK - 0 + @term_supported = true when -1 # ERR - case errret - when 1 - raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.') - when 0 - raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.') - when -1 - raise TerminfoError.new('The terminfo database could not be found.') - else # unknown - -1 - end - else # unknown - -2 + @term_supported = false end end @@ -148,9 +136,14 @@ def self.tigetnum(capname) num end + # NOTE: This means Fiddle and curses are enabled. def self.enabled? true end + + def self.term_supported? + @term_supported + end end if Reline::Terminfo.curses_dl module Reline::Terminfo diff --git a/test/reline/test_ansi_with_terminfo.rb b/test/reline/test_ansi_with_terminfo.rb index 13874606598246..e1c56b9ee12b23 100644 --- a/test/reline/test_ansi_with_terminfo.rb +++ b/test/reline/test_ansi_with_terminfo.rb @@ -109,4 +109,4 @@ def test_more_emacs assert_key_binding("\e ", :em_set_mark, [:emacs]) assert_key_binding("\C-x\C-x", :em_exchange_mark, [:emacs]) end -end if Reline::Terminfo.enabled? +end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? diff --git a/test/reline/test_terminfo.rb b/test/reline/test_terminfo.rb index dda9b324954715..4e59c5483864f9 100644 --- a/test/reline/test_terminfo.rb +++ b/test/reline/test_terminfo.rb @@ -58,4 +58,4 @@ def test_tigetnum_with_error assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum('unknown') } assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum(nil) } end -end if Reline::Terminfo.enabled? +end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? From b006919200d30daa672951edafd47c8e7af247d4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Apr 2024 18:00:56 +0900 Subject: [PATCH 006/135] `objspace_each_pages` is also only used if GC compression is possible --- gc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 787f5c765c5423..e5265616688551 100644 --- a/gc.c +++ b/gc.c @@ -3729,6 +3729,7 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void objspace_each_exec(protected, &each_obj_data); } +#if GC_CAN_COMPILE_COMPACTION static void objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void *data, bool protected) { @@ -3740,6 +3741,7 @@ objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void }; objspace_each_exec(protected, &each_obj_data); } +#endif struct os_each_struct { size_t num; @@ -10544,7 +10546,7 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do /* Find out which pool has the most pages */ size_t max_existing_pages = 0; - for(int i = 0; i < SIZE_POOL_COUNT; i++) { + for (int i = 0; i < SIZE_POOL_COUNT; i++) { rb_size_pool_t *size_pool = &size_pools[i]; rb_heap_t *heap = SIZE_POOL_EDEN_HEAP(size_pool); max_existing_pages = MAX(max_existing_pages, heap->total_pages); From 9b5d4274a247567c583667f4c8442872d8765d39 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 8 Mar 2024 13:57:05 +0900 Subject: [PATCH 007/135] [Feature #20329] Clean up dump sub-options Restructure `insns_without_opt` and `parsetree_with_comment` as `insns+without_opt` and `parsetree+with_comment` respectively, like `+error-tolerant`. --- ruby.c | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/ruby.c b/ruby.c index b7f0f5dedff9fb..32ac23b4042096 100644 --- a/ruby.c +++ b/ruby.c @@ -151,22 +151,24 @@ enum feature_flag_bits { SEP \ X(parsetree) \ SEP \ - X(parsetree_with_comment) \ - SEP \ X(insns) \ - SEP \ - X(insns_without_opt) \ /* END OF DUMPS */ enum dump_flag_bits { dump_version_v, dump_error_tolerant, + dump_with_comment, + dump_without_opt, EACH_DUMPS(DEFINE_DUMP, COMMA), dump_error_tolerant_bits = (DUMP_BIT(yydebug) | - DUMP_BIT(parsetree) | - DUMP_BIT(parsetree_with_comment)), + DUMP_BIT(parsetree)), + dump_with_comment_bits = (DUMP_BIT(yydebug) | + DUMP_BIT(parsetree)), + dump_without_opt_bits = (DUMP_BIT(insns)), dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) | - DUMP_BIT(parsetree) | DUMP_BIT(parsetree_with_comment) | - DUMP_BIT(insns) | DUMP_BIT(insns_without_opt)) + DUMP_BIT(parsetree) | DUMP_BIT(insns)), + dump_optional_bits = (DUMP_BIT(error_tolerant) | + DUMP_BIT(with_comment) | + DUMP_BIT(without_opt)) }; static inline void @@ -372,11 +374,9 @@ usage(const char *name, int help, int highlight, int columns) M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."), }; static const struct ruby_opt_message dumps[] = { - M("insns", "", "Instruction sequences."), - M("insns_without_opt", "", "Instruction sequences compiled with no optimization."), - M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator."), - M("parsetree(+error-tolerant)", "", "Abstract syntax tree (AST)."), - M("parsetree_with_comment(+error-tolerant)", "", "AST with comments."), + M("insns(+without-opt)", "", "Instruction sequences."), + M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator."), + M("parsetree(+error-tolerant, +with_comment)", "", "Abstract syntax tree (AST)."), }; static const struct ruby_opt_message features[] = { M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."), @@ -1103,15 +1103,25 @@ dump_additional_option(const char *str, int len, unsigned int bits, const char * int w; for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) { w = memtermspn(str, additional_opt_sep, len); -#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) { \ +#define SET_ADDITIONAL_BIT(bit) { \ if (bits & DUMP_BIT(bit)) \ rb_warn("duplicate option to dump %s: '%.*s'", name, w, str); \ bits |= DUMP_BIT(bit); \ continue; \ } +#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) SET_ADDITIONAL_BIT(bit) +#define SET_ADDITIONAL_2(bit, alias) \ + if (NAME_MATCH_P(#bit, str, w) || NAME_MATCH_P(#alias, str, w)) SET_ADDITIONAL_BIT(bit) + if (dump_error_tolerant_bits & bits) { SET_ADDITIONAL(error_tolerant); } + if (dump_with_comment_bits & bits) { + SET_ADDITIONAL_2(with_comment, comment); + } + if (dump_without_opt_bits & bits) { + SET_ADDITIONAL(without_opt); + } rb_warn("don't know how to dump %s with '%.*s'", name, w, str); } return bits; @@ -2497,10 +2507,10 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) if (!dump) return Qtrue; } - if (dump & (DUMP_BIT(parsetree)|DUMP_BIT(parsetree_with_comment))) { + if (dump & DUMP_BIT(parsetree)) { VALUE tree; if (result.ast) { - int comment = dump & DUMP_BIT(parsetree_with_comment); + int comment = opt->dump & DUMP_BIT(with_comment); tree = rb_parser_dump_tree(result.ast->body.root, comment); } else { @@ -2508,7 +2518,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } rb_io_write(rb_stdout, tree); rb_io_flush(rb_stdout); - dump &= ~DUMP_BIT(parsetree)&~DUMP_BIT(parsetree_with_comment); + dump &= ~DUMP_BIT(parsetree); if (!dump) { dispose_result(); return Qtrue; @@ -2533,7 +2543,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding); const struct rb_block *base_block = toplevel_context(toplevel_binding); const rb_iseq_t *parent = vm_block_iseq(base_block); - bool optimize = !(dump & DUMP_BIT(insns_without_opt)); + bool optimize = !(opt->dump & DUMP_BIT(without_opt)); if (!result.ast) { pm_parse_result_t *pm = &result.prism; @@ -2547,7 +2557,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } } - if (dump & (DUMP_BIT(insns) | DUMP_BIT(insns_without_opt))) { + if (dump & DUMP_BIT(insns)) { rb_io_write(rb_stdout, rb_iseq_disasm((const rb_iseq_t *)iseq)); rb_io_flush(rb_stdout); dump &= ~DUMP_BIT(insns); From df8f1f78f03ba0b18c082f2cd85a276862a1a944 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Mar 2024 23:22:30 +0900 Subject: [PATCH 008/135] [Feature #20329] Separate additional flags from main dump options Additional flags are comma separated list preceeded by `-` or `+`. Before: ```sh $ ruby --dump=insns+without_opt ``` After: ```sh $ ruby --dump=insns-opt,-optimize ``` At the same time, `parsetree_with_comment` is split to `parsetree` option and additional `comment` flag. Before: ```sh $ ruby --dump=parsetree_with_comment ``` After: ```sh $ ruby --dump=parsetree,+comment ``` Also flags can be separate `--dump`. ```sh $ ruby --dump=parsetree --dump=+comment --dump=+error_tolerant ``` Ineffective flags are ignored silently. ```sh $ ruby --dump=parsetree --dump=+comment --dump=+error_tolerant ``` --- ruby.c | 101 ++++++++++++++++++++-------------- test/ruby/test_rubyoptions.rb | 15 ++++- 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ruby.c b/ruby.c index 32ac23b4042096..25d9e23b5d68b0 100644 --- a/ruby.c +++ b/ruby.c @@ -155,20 +155,15 @@ enum feature_flag_bits { /* END OF DUMPS */ enum dump_flag_bits { dump_version_v, - dump_error_tolerant, - dump_with_comment, - dump_without_opt, + dump_opt_error_tolerant, + dump_opt_comment, + dump_opt_optimize, EACH_DUMPS(DEFINE_DUMP, COMMA), - dump_error_tolerant_bits = (DUMP_BIT(yydebug) | - DUMP_BIT(parsetree)), - dump_with_comment_bits = (DUMP_BIT(yydebug) | - DUMP_BIT(parsetree)), - dump_without_opt_bits = (DUMP_BIT(insns)), dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) | DUMP_BIT(parsetree) | DUMP_BIT(insns)), - dump_optional_bits = (DUMP_BIT(error_tolerant) | - DUMP_BIT(with_comment) | - DUMP_BIT(without_opt)) + dump_optional_bits = (DUMP_BIT(opt_error_tolerant) | + DUMP_BIT(opt_comment) | + DUMP_BIT(opt_optimize)) }; static inline void @@ -224,6 +219,7 @@ cmdline_options_init(ruby_cmdline_options_t *opt) #elif defined(YJIT_FORCE_ENABLE) opt->features.set |= FEATURE_BIT(yjit); #endif + opt->dump |= DUMP_BIT(opt_optimize); opt->backtrace_length_limit = LONG_MIN; return opt; @@ -374,9 +370,12 @@ usage(const char *name, int help, int highlight, int columns) M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."), }; static const struct ruby_opt_message dumps[] = { - M("insns(+without-opt)", "", "Instruction sequences."), - M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator."), - M("parsetree(+error-tolerant, +with_comment)", "", "Abstract syntax tree (AST)."), + M("insns", "", "Instruction sequences."), + M("yydebug", "", "yydebug of yacc parser generator."), + M("parsetree", "", "Abstract syntax tree (AST)."), + M("-optimize", "", "Disable optimization (affects insns)."), + M("+error-tolerant", "", "Error-tolerant parsing (affects yydebug, parsetree)."), + M("+comment", "", "Add comments to AST (affects parsetree)."), }; static const struct ruby_opt_message features[] = { M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."), @@ -978,7 +977,7 @@ name_match_p(const char *name, const char *str, size_t len) if (*str != '-' && *str != '_') return 0; while (ISALNUM(*name)) name++; if (*name != '-' && *name != '_') return 0; - ++name; + if (!*++name) return 1; ++str; if (--len == 0) return 1; } @@ -1098,31 +1097,45 @@ memtermspn(const char *str, char term, int len) static const char additional_opt_sep = '+'; static unsigned int -dump_additional_option(const char *str, int len, unsigned int bits, const char *name) +dump_additional_option_flag(const char *str, int len, unsigned int bits, bool set) +{ +#define SET_DUMP_OPT(bit) if (NAME_MATCH_P(#bit, str, len)) { \ + return set ? (bits | DUMP_BIT(opt_ ## bit)) : (bits & ~DUMP_BIT(opt_ ## bit)); \ + } + SET_DUMP_OPT(error_tolerant); + SET_DUMP_OPT(comment); + SET_DUMP_OPT(optimize); +#undef SET_DUMP_OPT + rb_warn("don't know how to dump with%s '%.*s'", set ? "" : "out", len, str); + return bits; +} + +static unsigned int +dump_additional_option(const char *str, int len, unsigned int bits) { int w; for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) { w = memtermspn(str, additional_opt_sep, len); -#define SET_ADDITIONAL_BIT(bit) { \ - if (bits & DUMP_BIT(bit)) \ - rb_warn("duplicate option to dump %s: '%.*s'", name, w, str); \ - bits |= DUMP_BIT(bit); \ - continue; \ - } -#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) SET_ADDITIONAL_BIT(bit) -#define SET_ADDITIONAL_2(bit, alias) \ - if (NAME_MATCH_P(#bit, str, w) || NAME_MATCH_P(#alias, str, w)) SET_ADDITIONAL_BIT(bit) - - if (dump_error_tolerant_bits & bits) { - SET_ADDITIONAL(error_tolerant); + bool set = true; + if (*str == '-' || *str == '+') { + set = *str++ == '+'; + --w; } - if (dump_with_comment_bits & bits) { - SET_ADDITIONAL_2(with_comment, comment); - } - if (dump_without_opt_bits & bits) { - SET_ADDITIONAL(without_opt); + else { + int n = memtermspn(str, '-', w); + if (str[n] == '-') { + if (NAME_MATCH_P("with", str, n)) { + str += n; + w -= n; + } + else if (NAME_MATCH_P("without", str, n)) { + set = false; + str += n; + w -= n; + } + } } - rb_warn("don't know how to dump %s with '%.*s'", name, w, str); + bits = dump_additional_option_flag(str, w, bits, set); } return bits; } @@ -1131,12 +1144,17 @@ static void dump_option(const char *str, int len, void *arg) { static const char list[] = EACH_DUMPS(LITERAL_NAME_ELEMENT, ", "); + unsigned int *bits_ptr = (unsigned int *)arg; + if (*str == '+' || *str == '-') { + bool set = *str++ == '+'; + *bits_ptr = dump_additional_option_flag(str, --len, *bits_ptr, set); + return; + } int w = memtermspn(str, additional_opt_sep, len); #define SET_WHEN_DUMP(bit) \ - if (NAME_MATCH_P(#bit, (str), (w))) { \ - *(unsigned int *)arg |= \ - dump_additional_option(str + w, len - w, DUMP_BIT(bit), #bit); \ + if (NAME_MATCH_P(#bit "-", (str), (w))) { \ + *bits_ptr = dump_additional_option(str + w, len - w, *bits_ptr | DUMP_BIT(bit)); \ return; \ } EACH_DUMPS(SET_WHEN_DUMP, ;); @@ -2059,12 +2077,13 @@ process_script(ruby_cmdline_options_t *opt) { rb_ast_t *ast; VALUE parser = rb_parser_new(); + const unsigned int dump = opt->dump; - if (opt->dump & DUMP_BIT(yydebug)) { + if (dump & DUMP_BIT(yydebug)) { rb_parser_set_yydebug(parser, Qtrue); } - if (opt->dump & DUMP_BIT(error_tolerant)) { + if ((dump & dump_exit_bits) && (dump & DUMP_BIT(opt_error_tolerant))) { rb_parser_error_tolerant(parser); } @@ -2510,7 +2529,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) if (dump & DUMP_BIT(parsetree)) { VALUE tree; if (result.ast) { - int comment = opt->dump & DUMP_BIT(with_comment); + int comment = opt->dump & DUMP_BIT(opt_comment); tree = rb_parser_dump_tree(result.ast->body.root, comment); } else { @@ -2543,7 +2562,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding); const struct rb_block *base_block = toplevel_context(toplevel_binding); const rb_iseq_t *parent = vm_block_iseq(base_block); - bool optimize = !(opt->dump & DUMP_BIT(without_opt)); + bool optimize = (opt->dump & DUMP_BIT(opt_optimize)) != 0; if (!result.ast) { pm_parse_result_t *pm = &result.prism; diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 8396066dc1ba19..8ea42251394c97 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1138,17 +1138,17 @@ def test_pflag_sub assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157) end - def assert_norun_with_rflag(*opt) + def assert_norun_with_rflag(*opt, test_stderr: []) bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option" stderr = [] Tempfile.create(%w"bug10435- .rb") do |script| dir, base = File.split(script.path) File.write(script, "abort ':run'\n") opts = ['-C', dir, '-r', "./#{base}", *opt] - _, e = assert_in_out_err([*opts, '-ep'], "", //) + _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr) stderr.concat(e) if e stderr << "---" - _, e = assert_in_out_err([*opts, base], "", //) + _, e = assert_in_out_err([*opts, base], "", //, test_stderr) stderr.concat(e) if e end assert_not_include(stderr, ":run", bug10435) @@ -1171,6 +1171,15 @@ def test_dump_parsetree_with_rflag assert_norun_with_rflag('--dump=parse+error_tolerant') end + def test_dump_parsetree_error_tolerant + assert_in_out_err(['--dump=parse', '-e', 'begin'], + "", [], /unexpected end-of-input/, success: false) + assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'], + "", /^# @/, /unexpected end-of-input/, success: true) + assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'], + "", [], /unexpected end-of-input/, success: false) + end + def test_dump_insns_with_rflag assert_norun_with_rflag('--dump=insns') end From 977672731fa7ce9d22b8b3c997acc54a9365d2c3 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Apr 2024 19:12:39 +0900 Subject: [PATCH 009/135] Fix a variable name The first argument of `WARN_SPACE_CHAR` is always `c2` in caller side, so `c` equals to `c2`. --- parse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse.y b/parse.y index 3824321731b145..d8f3473f932b0a 100644 --- a/parse.y +++ b/parse.y @@ -8129,7 +8129,7 @@ escaped_control_code(int c) } #define WARN_SPACE_CHAR(c, prefix) \ - rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c2)) + rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c)) static int tokadd_codepoint(struct parser_params *p, rb_encoding **encp, From 02f5e627a49c28beb421c869e4e1d4525d59d8ac Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Apr 2024 19:05:53 +0900 Subject: [PATCH 010/135] Remove redundant conversion between int and object --- parse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parse.y b/parse.y index d8f3473f932b0a..291f9f549187aa 100644 --- a/parse.y +++ b/parse.y @@ -1830,7 +1830,7 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) s # define WARN_I(i) i # define WARN_ID(i) rb_id2name(i) -# define WARN_IVAL(i) NUM2INT(i) +# define WARN_IVAL(i) i # define PRIsWARN PRIsVALUE # define WARN_ARGS(fmt,n) WARN_ARGS_L(p->ruby_sourceline,fmt,n) # define WARN_ARGS_L(l,fmt,n) p->ruby_sourcefile, (l), (fmt) @@ -13462,7 +13462,7 @@ rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE * st_data_t line; if (st_lookup(p->case_labels, (st_data_t)arg, &line)) { rb_warning1("duplicated 'when' clause with line %d is ignored", - WARN_IVAL(INT2NUM((int)line))); + WARN_IVAL((int)line)); return; } } From 0b4db711dd43ea437093428ea72a08a2a6ef9f88 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Apr 2024 19:09:36 +0900 Subject: [PATCH 011/135] Remove unused function from `struct rb_parser_config_struct` --- ruby_parser.c | 1 - rubyparser.h | 1 - universal_parser.c | 2 -- 3 files changed, 4 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index b3617d5b5b3025..76dfa3b7be4203 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -483,7 +483,6 @@ static const rb_parser_config_t rb_global_parser_config = { .filesystem_str_new_cstr = rb_filesystem_str_new_cstr, .obj_as_string = rb_obj_as_string, - .num2int = rb_num2int_inline, .int2num = rb_int2num_inline, .stderr_tty_p = rb_stderr_tty_p, diff --git a/rubyparser.h b/rubyparser.h index 1705097f7ad3c8..f6fafa473fd87e 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1310,7 +1310,6 @@ typedef struct rb_parser_config_struct { VALUE (*obj_as_string)(VALUE); /* Numeric */ - int (*num2int)(VALUE val); VALUE (*int2num)(int v); /* IO */ diff --git a/universal_parser.c b/universal_parser.c index 80a6f5d14beac3..b8b1cb9f846a48 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -156,8 +156,6 @@ #define rb_filesystem_str_new_cstr p->config->filesystem_str_new_cstr #define rb_obj_as_string p->config->obj_as_string -#undef NUM2INT -#define NUM2INT p->config->num2int #undef INT2NUM #define INT2NUM p->config->int2num From ad90fdd24c55284deab8c24449bfd1113658afdf Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 4 Apr 2024 19:32:30 -0700 Subject: [PATCH 012/135] Remove compiler code to handle blocks in attrasgn Passing blocks is no longer allowed in attrasgn. This is similar to 3a674c9c655288b3e12ac1cff149ba4af08fd452, but for attrasgn instead of op_asgn. --- compile.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/compile.c b/compile.c index dfdbb8f6445a9b..d509957dac2432 100644 --- a/compile.c +++ b/compile.c @@ -9782,16 +9782,7 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node ADD_SEQ(ret, recv); ADD_SEQ(ret, args); - if (flag & VM_CALL_ARGS_BLOCKARG) { - ADD_INSN1(ret, node, topn, INT2FIX(1)); - if (flag & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, node, putobject, INT2FIX(-1)); - ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); - } - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 3)); - ADD_INSN (ret, node, pop); - } - else if (flag & VM_CALL_ARGS_SPLAT) { + if (flag & VM_CALL_ARGS_SPLAT) { ADD_INSN(ret, node, dup); ADD_INSN1(ret, node, putobject, INT2FIX(-1)); ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); From 7767db237935135e14cb0f9ca70aa3a8dea03faa Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 7 Apr 2024 00:42:17 +0900 Subject: [PATCH 013/135] Fix ripper to dispatch warning event for duplicated when clause Need to separate `check_literal_when` function for parser and ripper otherwise warning event is not dispatched because parser `rb_warning1` is used in ripper. --- internal/parse.h | 1 - parse.y | 24 +++++++++--------------- test/ripper/test_parser_events.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/internal/parse.h b/internal/parse.h index f4f2f0c0f521ca..d7fc6ddad4bfdb 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -65,7 +65,6 @@ int rb_ruby_parser_end_seen_p(rb_parser_t *p); int rb_ruby_parser_set_yydebug(rb_parser_t *p, int flag); rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); -void rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc); void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); diff --git a/parse.y b/parse.y index 291f9f549187aa..c828a7686c3d39 100644 --- a/parse.y +++ b/parse.y @@ -84,7 +84,6 @@ static NODE *reg_named_capture_assign(struct parser_params* p, VALUE regexp, con VALUE rb_io_gets_internal(VALUE io); #endif /* !UNIVERSAL_PARSER */ -#ifndef RIPPER static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); static int @@ -210,7 +209,6 @@ literal_hash(st_data_t a) #endif } } -#endif /* !RIPPER */ static inline int parse_isascii(int c) @@ -1491,6 +1489,8 @@ int reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); static int literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_string_t *tail); static NODE *heredoc_dedent(struct parser_params*,NODE*); +static void check_literal_when(struct parser_params *p, NODE *args, const YYLTYPE *loc); + #ifdef RIPPER static VALUE var_field(struct parser_params *p, VALUE a); #define get_value(idx) (rb_ary_entry(p->s_value_stack, idx)) @@ -2038,7 +2038,6 @@ get_nd_args(struct parser_params *p, NODE *node) } } -#ifndef RIPPER static st_index_t djb2(const uint8_t *str, size_t len) { @@ -2056,7 +2055,6 @@ parser_memhash(const void *ptr, long len) { return djb2(ptr, len); } -#endif #define PARSER_STRING_PTR(str) (str->ptr) #define PARSER_STRING_LEN(str) (str->len) @@ -2120,7 +2118,6 @@ rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *str) xfree(str); } -#ifndef RIPPER static st_index_t rb_parser_str_hash(rb_parser_string_t *str) { @@ -2132,7 +2129,6 @@ rb_char_p_hash(const char *c) { return parser_memhash((const void *)c, strlen(c)); } -#endif static size_t rb_parser_str_capacity(rb_parser_string_t *str, const int termlen) @@ -2528,7 +2524,6 @@ rb_parser_str_resize(struct parser_params *p, rb_parser_string_t *str, long len) return str; } -#ifndef RIPPER # define PARSER_ENC_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ ((ptrvar) = str->ptr, \ (lenvar) = str->len, \ @@ -2549,6 +2544,7 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) memcmp(ptr1, ptr2, len1) != 0); } +#ifndef RIPPER static void rb_parser_ary_extend(rb_parser_t *p, rb_parser_ary_t *ary, long len) { @@ -5376,7 +5372,7 @@ do_body : { case_args : arg_value { - rb_parser_check_literal_when(p, $1, &@1); + check_literal_when(p, $1, &@1); $$ = NEW_LIST($1, &@$); /*% ripper: args_add!(args_new!, $:1) %*/ } @@ -5387,7 +5383,7 @@ case_args : arg_value } | case_args ',' arg_value { - rb_parser_check_literal_when(p, $3, &@3); + check_literal_when(p, $3, &@3); $$ = last_arg_append(p, $1, $3, &@$); /*% ripper: args_add!($:1, $:3) %*/ } @@ -13439,7 +13435,6 @@ new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER static const struct st_hash_type literal_type = { literal_cmp, @@ -13448,8 +13443,8 @@ struct st_hash_type literal_type = { static int nd_type_st_key_enable_p(NODE *node); -void -rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) +static void +check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) { /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ if (!arg || !p->case_labels) return; @@ -13462,13 +13457,12 @@ rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE * st_data_t line; if (st_lookup(p->case_labels, (st_data_t)arg, &line)) { rb_warning1("duplicated 'when' clause with line %d is ignored", - WARN_IVAL((int)line)); + WARN_I((int)line)); return; } } st_insert(p->case_labels, (st_data_t)arg, (st_data_t)p->ruby_sourceline); } -#endif #ifdef RIPPER static int @@ -14860,7 +14854,6 @@ dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER static int nd_type_st_key_enable_p(NODE *node) { @@ -14881,6 +14874,7 @@ nd_type_st_key_enable_p(NODE *node) } } +#ifndef RIPPER static VALUE nd_value(struct parser_params *p, NODE *node) { diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 9283a4d3434391..0bad60b2596033 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -1672,6 +1672,20 @@ def test_warning_invalid_magic_comment assert_equal(%w"frozen_string_literal nottrue", args) end + def test_warning_duplicated_when_clause + fmt, *args = warning(<<~STR) + a = 1 + case a + when 1 + when 1 + when 2 + else + end + STR + assert_match(/duplicated 'when' clause/, fmt) + assert_equal([3], args) + end + def test_warn_cr_in_middle fmt = nil assert_warn("") {fmt, = warn("\r;")} From 70a0dd41629de7760a38e9530dd58ad641e11b82 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 7 Apr 2024 09:46:11 +0900 Subject: [PATCH 014/135] Remove unused macro --- parse.y | 2 -- 1 file changed, 2 deletions(-) diff --git a/parse.y b/parse.y index c828a7686c3d39..befc84c6d496ac 100644 --- a/parse.y +++ b/parse.y @@ -1807,7 +1807,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) STR_NEW2(s) # define WARN_I(i) INT2NUM(i) # define WARN_ID(i) rb_id2str(i) -# define WARN_IVAL(i) i # define PRIsWARN PRIsVALUE # define rb_warn0L_experimental(l,fmt) WARN_CALL(WARN_ARGS_L(l, fmt, 1)) # define WARN_ARGS(fmt,n) p->value, id_warn, n, rb_usascii_str_new_lit(fmt) @@ -1830,7 +1829,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) s # define WARN_I(i) i # define WARN_ID(i) rb_id2name(i) -# define WARN_IVAL(i) i # define PRIsWARN PRIsVALUE # define WARN_ARGS(fmt,n) WARN_ARGS_L(p->ruby_sourceline,fmt,n) # define WARN_ARGS_L(l,fmt,n) p->ruby_sourcefile, (l), (fmt) From 6bfabd076b99bd47ee10277b8de24aaa791be69b Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 7 Apr 2024 09:56:47 +0900 Subject: [PATCH 015/135] Remove undefined function's prototype declaration --- internal/parse.h | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/parse.h b/internal/parse.h index d7fc6ddad4bfdb..8e82c0f8974a00 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -68,7 +68,6 @@ rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); -VALUE rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); From eaa6e4b22862a31c9595d052f7c63ce997a95d68 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Apr 2024 13:35:20 +0900 Subject: [PATCH 016/135] [DOC] Update PTY documents - Fix markups as RDoc. - Remove already descriptions of removed methods. --- doc/pty/README.expect.ja | 32 +++++++++++++------------ doc/pty/README.ja | 50 ++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/doc/pty/README.expect.ja b/doc/pty/README.expect.ja index 7c0456f24fe504..a4eb6b01df24a0 100644 --- a/doc/pty/README.expect.ja +++ b/doc/pty/README.expect.ja @@ -1,21 +1,23 @@ - README for expect += README for expect by A. Ito, 28 October, 1998 - Expectライブラリは,tcl の expect パッケージと似たような機能を +Expectライブラリは,tcl の expect パッケージと似たような機能を IOクラスに追加します. - 追加されるメソッドの使い方は次の通りです. +追加されるメソッドの使い方は次の通りです. - IO#expect(pattern,timeout=9999999) +[IO#expect(pattern,timeout=9999999)] -pattern は String か Regexp のインスタンス,timeout は Fixnum -のインスタンスです.timeout は省略できます. - このメソッドがブロックなしで呼ばれた場合,まずレシーバである -IOオブジェクトから pattern にマッチするパターンが読みこまれる -まで待ちます.パターンが得られたら,そのパターンに関する配列を -返します.配列の最初の要素は,pattern にマッチするまでに読みこ -まれた内容の文字列です.2番目以降の要素は,pattern の正規表現 -の中にアンカーがあった場合に,そのアンカーにマッチする部分です. -もしタイムアウトが起きた場合は,このメソッドはnilを返します. - このメソッドがブロック付きで呼ばれた場合には,マッチした要素の -配列がブロック引数として渡され,ブロックが評価されます. + _pattern_ は String か Regexp のインスタンス,_timeout_ は Fixnum + のインスタンスです._timeout_ は省略できます. + + このメソッドがブロックなしで呼ばれた場合,まずレシーバである + IOオブジェクトから _pattern_ にマッチするパターンが読みこまれる + まで待ちます.パターンが得られたら,そのパターンに関する配列を + 返します.配列の最初の要素は,_pattern_ にマッチするまでに読みこ + まれた内容の文字列です.2番目以降の要素は,_pattern_ の正規表現 + の中にアンカーがあった場合に,そのアンカーにマッチする部分です. + もしタイムアウトが起きた場合は,このメソッドは +nil+ を返します. + + このメソッドがブロック付きで呼ばれた場合には,マッチした要素の + 配列がブロック引数として渡され,ブロックが評価されます. diff --git a/doc/pty/README.ja b/doc/pty/README.ja index 2d83ffa033e067..a26b4932ff904e 100644 --- a/doc/pty/README.ja +++ b/doc/pty/README.ja @@ -1,27 +1,26 @@ -pty 拡張モジュール version 0.3 by A.ito += pty 拡張モジュール version 0.3 by A.ito 1. はじめに -この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを -実行する機能を ruby に提供します. + この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを + 実行する機能を ruby に提供します. 2. インストール -次のようにしてインストールしてください. + 次のようにしてインストールしてください. -(1) ruby extconf.rb + 1. ruby extconf.rb + を実行すると Makefile が生成されます. - を実行すると Makefile が生成されます. - -(2) make; make install を実行してください. + 2. make; make install を実行してください. 3. 何ができるか -この拡張モジュールは,PTY というモジュールを定義します.その中 -には,次のようなモジュール関数が含まれています. + この拡張モジュールは,PTY というモジュールを定義します.その中 + には,次のようなモジュール関数が含まれています. - getpty(command) - spawn(command) + [PTY.getpty(command)] + [PTY.spawn(command)] この関数は,仮想ttyを確保し,指定されたコマンドをその仮想tty の向こうで実行し,配列を返します.戻り値は3つの要素からなる @@ -35,12 +34,7 @@ pty 拡張モジュール version 0.3 by A.ito のみ例外が発生します.子プロセスをモニターしているスレッドはブロッ クを抜けるときに終了します. - protect_signal - reset_signal - - 廃止予定です. - - PTY.open + [PTY.open] 仮想ttyを確保し,マスター側に対応するIOオブジェクトとスレーブ側に 対応するFileオブジェクトの配列を返します.ブロック付きで呼び出さ @@ -48,7 +42,7 @@ pty 拡張モジュール version 0.3 by A.ito クから返された結果を返します.また、このマスターIOとスレーブFile は、ブロックを抜けるときにクローズ済みでなければクローズされます. - PTY.check(pid[, raise=false]) + [PTY.check(pid[, raise=false])] pidで指定された子プロセスの状態をチェックし,実行中であればnilを 返します.終了しているか停止している場合、第二引数が偽であれば、 @@ -57,20 +51,20 @@ pty 拡張モジュール version 0.3 by A.ito 4. 利用について -伊藤彰則が著作権を保有します. + 伊藤彰則が著作権を保有します. -ソースプログラムまたはドキュメントに元の著作権表示が改変されずに -表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 -権者に無断で利用・配布・改変できます.利用目的は限定されていませ -ん. + ソースプログラムまたはドキュメントに元の著作権表示が改変されずに + 表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 + 権者に無断で利用・配布・改変できます.利用目的は限定されていませ + ん. -このプログラムの利用・配布その他このプログラムに関係する行為によ -って生じたいかなる損害に対しても,作者は一切責任を負いません. + このプログラムの利用・配布その他このプログラムに関係する行為によ + って生じたいかなる損害に対しても,作者は一切責任を負いません. 5. バグ報告等 -バグレポートは歓迎します. + バグレポートは歓迎します. aito@ei5sun.yz.yamagata-u.ac.jp -まで電子メールでバグレポートをお送りください. + まで電子メールでバグレポートをお送りください. From dfa0897de89251a631a67460b941cd24a14c9b55 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 7 Apr 2024 19:18:09 +1200 Subject: [PATCH 017/135] Enumerator should use a non-blocking fiber. (#10478) --- cont.c | 2 +- internal/parse.h | 1 + test/fiber/test_enumerator.rb | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cont.c b/cont.c index c6a94e0709f369..f7a4863f2cd270 100644 --- a/cont.c +++ b/cont.c @@ -2382,7 +2382,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); } VALUE diff --git a/internal/parse.h b/internal/parse.h index 8e82c0f8974a00..d7fc6ddad4bfdb 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -68,6 +68,7 @@ rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); +VALUE rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); diff --git a/test/fiber/test_enumerator.rb b/test/fiber/test_enumerator.rb index 40f7d0172531dc..e9410f925c3b3a 100644 --- a/test/fiber/test_enumerator.rb +++ b/test/fiber/test_enumerator.rb @@ -42,4 +42,12 @@ def test_read_characters assert_predicate(i, :closed?) assert_predicate(o, :closed?) end + + def enumerator_fiber_is_nonblocking + enumerator = Enumerator.new do |yielder| + yielder << Fiber.current.blocking? + end + + assert_equal(false, enumerator.next) + end end From b5b54c19f65a42c9229eff5e978937222d46ca41 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Apr 2024 15:28:07 +0900 Subject: [PATCH 018/135] Unify `ERRMSG1` and `ERRMSG2` to `ERRMSG_FMT` variadic macro --- process.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/process.c b/process.c index 72483815d68f1b..11d5fc999c8aeb 100644 --- a/process.c +++ b/process.c @@ -3140,9 +3140,13 @@ f_exec(int c, const VALUE *a, VALUE _) UNREACHABLE_RETURN(Qnil); } -#define ERRMSG(str) do { if (errmsg && 0 < errmsg_buflen) strlcpy(errmsg, (str), errmsg_buflen); } while (0) -#define ERRMSG1(str, a) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a)); } while (0) -#define ERRMSG2(str, a, b) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a), (b)); } while (0) +#define ERRMSG(str) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)strlcpy(errmsg, (str), errmsg_buflen) : (void)0) + +#define ERRMSG_FMT(...) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)snprintf(errmsg, errmsg_buflen, __VA_ARGS__) : (void)0) static int fd_get_cloexec(int fd, char *errmsg, size_t errmsg_buflen); static int fd_set_cloexec(int fd, char *errmsg, size_t errmsg_buflen); From ed4d03ea41fa48e2d83f5956796f47c32fbe27ab Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Aug 2023 08:06:08 +0900 Subject: [PATCH 019/135] Make `dln_warning` a variadic macro --- dln_find.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dln_find.c b/dln_find.c index 91c51394a95463..ae37b9dde42426 100644 --- a/dln_find.c +++ b/dln_find.c @@ -11,11 +11,9 @@ #ifdef RUBY_EXPORT #include "ruby/ruby.h" -#define dln_warning rb_warning -#define dln_warning_arg +#define dln_warning(...) rb_warning(__VA_ARGS__) #else -#define dln_warning fprintf -#define dln_warning_arg stderr, +#define dln_warning(...) fprintf(stderr, __VA_ARGS__) #endif #include "dln.h" @@ -109,7 +107,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, static const char pathname_too_long[] = "openpath: pathname too long (ignored)\n\ \tDirectory \"%.*s\"%s\n\tFile \"%.*s\"%s\n"; -#define PATHNAME_TOO_LONG() dln_warning(dln_warning_arg pathname_too_long, \ +#define PATHNAME_TOO_LONG() dln_warning(pathname_too_long, \ ((bp - fbuf) > 100 ? 100 : (int)(bp - fbuf)), fbuf, \ ((bp - fbuf) > 100 ? "..." : ""), \ (fnlen > 100 ? 100 : (int)fnlen), fname, \ @@ -120,8 +118,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, RETURN_IF(!fname); fnlen = strlen(fname); if (fnlen >= size) { - dln_warning(dln_warning_arg - "openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", + dln_warning("openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", (fnlen > 100 ? 100 : (int)fnlen), fname, (fnlen > 100 ? "..." : "")); return NULL; From bdb1fc1e5bd09f23860d0f3aced10da51d2c9867 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 7 Apr 2024 19:57:15 +1200 Subject: [PATCH 020/135] Prefer to use `Fiber#transfer` in scheduler implementation. (#10479) --- test/fiber/scheduler.rb | 22 ++++++++++++++-------- test/fiber/test_mutex.rb | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 5090271db157db..3926226ca334d9 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -27,7 +27,9 @@ class Scheduler Warning[:experimental] = experimental end - def initialize + def initialize(fiber = Fiber.current) + @fiber = fiber + @readable = {} @writable = {} @waiting = {} @@ -45,6 +47,10 @@ def initialize attr :writable attr :waiting + def transfer + @fiber.transfer + end + def next_timeout _fiber, timeout = @waiting.min_by{|key, value| value} @@ -88,7 +94,7 @@ def run end selected.each do |fiber, events| - fiber.resume(events) + fiber.transfer(events) end if @waiting.any? @@ -98,7 +104,7 @@ def run waiting.each do |fiber, timeout| if fiber.alive? if timeout <= time - fiber.resume + fiber.transfer else @waiting[fiber] = timeout end @@ -114,7 +120,7 @@ def run end ready.each do |fiber| - fiber.resume + fiber.transfer end end end @@ -217,7 +223,7 @@ def io_wait(io, events, duration) @waiting[fiber] = current_time + duration end - Fiber.yield + @fiber.transfer ensure @waiting.delete(fiber) if duration @readable.delete(io) if readable @@ -254,7 +260,7 @@ def block(blocker, timeout = nil) if timeout @waiting[fiber] = current_time + timeout begin - Fiber.yield + @fiber.transfer ensure # Remove from @waiting in the case #unblock was called before the timeout expired: @waiting.delete(fiber) @@ -262,7 +268,7 @@ def block(blocker, timeout = nil) else @blocking[fiber] = true begin - Fiber.yield + @fiber.transfer ensure @blocking.delete(fiber) end @@ -290,7 +296,7 @@ def unblock(blocker, fiber) def fiber(&block) fiber = Fiber.new(blocking: false, &block) - fiber.resume + fiber.transfer return fiber end diff --git a/test/fiber/test_mutex.rb b/test/fiber/test_mutex.rb index 449c49f38bc81e..2cee2cc235684b 100644 --- a/test/fiber/test_mutex.rb +++ b/test/fiber/test_mutex.rb @@ -207,7 +207,7 @@ def test_mutex_deadlock Fiber.schedule do mutex.synchronize do puts 'in synchronize' - Fiber.yield + scheduler.transfer end end From 0620f006c2043c9842f2a431f14cf1d3f846fb07 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Aug 2023 08:06:18 +0900 Subject: [PATCH 021/135] Remove `translit_char` It has been used only for DOSISH other than Windows. --- eval_intern.h | 12 ------------ ruby.c | 2 -- 2 files changed, 14 deletions(-) diff --git a/eval_intern.h b/eval_intern.h index fa02f229122d54..9a551120ff3ec9 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -321,16 +321,4 @@ rb_char_next(const char *p) # endif #endif -#if defined DOSISH || defined __CYGWIN__ -static inline void -translit_char(char *p, int from, int to) -{ - while (*p) { - if ((unsigned char)*p == from) - *p = to; - p = CharNext(p); - } -} -#endif - #endif /* RUBY_EVAL_INTERN_H */ diff --git a/ruby.c b/ruby.c index 25d9e23b5d68b0..cf32aa6e50bcd3 100644 --- a/ruby.c +++ b/ruby.c @@ -2367,8 +2367,6 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) #ifdef _WIN32 translit_char_bin(RSTRING_PTR(opt->script_name), '\\', '/'); -#elif defined DOSISH - translit_char(RSTRING_PTR(opt->script_name), '\\', '/'); #endif ruby_gc_set_params(); From 0d93fd0f69cf6b8f60d76948b2e7d1144c1cd843 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Aug 2023 08:06:40 +0900 Subject: [PATCH 022/135] Merge `push_include_cygwin` into `push_include` --- ruby.c | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/ruby.c b/ruby.c index cf32aa6e50bcd3..7d154b58e868fc 100644 --- a/ruby.c +++ b/ruby.c @@ -444,34 +444,25 @@ push_include(const char *path, VALUE (*filter)(VALUE)) const char sep = PATH_SEP_CHAR; const char *p, *s; VALUE load_path = GET_VM()->load_path; - - p = path; - while (*p) { - while (*p == sep) - p++; - if (!*p) break; - for (s = p; *s && *s != sep; s = CharNext(s)); - rb_ary_push(load_path, (*filter)(rubylib_path_new(p, s - p))); - p = s; - } -} - #ifdef __CYGWIN__ -static void -push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) -{ - const char *p, *s; char rubylib[FILENAME_MAX]; VALUE buf = 0; +# define is_path_sep(c) ((c) == sep || (c) == ';') +#else +# define is_path_sep(c) ((c) == sep) +#endif p = path; while (*p) { - unsigned int len; - while (*p == ';') + long len; + while (is_path_sep(*p)) p++; if (!*p) break; - for (s = p; *s && *s != ';'; s = CharNext(s)); + for (s = p; *s && !is_path_sep(*s); s = CharNext(s)); len = s - p; +#undef is_path_sep + +#ifdef __CYGWIN__ if (*s) { if (!buf) { buf = rb_str_new(p, len); @@ -488,18 +479,17 @@ push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) #else # error no cygwin_conv_path #endif - if (CONV_TO_POSIX_PATH(p, rubylib) == 0) + if (CONV_TO_POSIX_PATH(p, rubylib) == 0) { p = rubylib; - push_include(p, filter); - if (!*s) break; - p = s + 1; + len = strlen(p); + } +#endif + rb_ary_push(load_path, (*filter)(rubylib_path_new(p, len))); + p = s; } } -#define push_include push_include_cygwin -#endif - -void +static void ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { if (path == 0) @@ -512,6 +502,7 @@ identical_path(VALUE path) { return path; } + static VALUE locale_path(VALUE path) { From b88e0d6653836a57f2a51c03e204850319d8e218 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Aug 2023 11:29:50 +0900 Subject: [PATCH 023/135] Merge `push_include` and `ruby_push_include` --- ruby.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ruby.c b/ruby.c index 7d154b58e868fc..323446089429f4 100644 --- a/ruby.c +++ b/ruby.c @@ -439,7 +439,7 @@ usage(const char *name, int help, int highlight, int columns) #define rubylib_path_new rb_str_new static void -push_include(const char *path, VALUE (*filter)(VALUE)) +ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { const char sep = PATH_SEP_CHAR; const char *p, *s; @@ -452,6 +452,7 @@ push_include(const char *path, VALUE (*filter)(VALUE)) # define is_path_sep(c) ((c) == sep) #endif + if (path == 0) return; p = path; while (*p) { long len; @@ -489,14 +490,6 @@ push_include(const char *path, VALUE (*filter)(VALUE)) } } -static void -ruby_push_include(const char *path, VALUE (*filter)(VALUE)) -{ - if (path == 0) - return; - push_include(path, filter); -} - static VALUE identical_path(VALUE path) { From b473d304d449b520a967e45b7d5d5ecb8556b4c3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 7 Apr 2024 22:20:22 +1200 Subject: [PATCH 024/135] Revert "Enumerator should use a non-blocking fiber. (#10478)" (#10480) This reverts commit dfa0897de89251a631a67460b941cd24a14c9b55. This commit accidentally included some change in `parse.h`. Reverting and re-applying the relevant changes. --- cont.c | 2 +- internal/parse.h | 1 - test/fiber/test_enumerator.rb | 8 -------- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/cont.c b/cont.c index f7a4863f2cd270..c6a94e0709f369 100644 --- a/cont.c +++ b/cont.c @@ -2382,7 +2382,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); } VALUE diff --git a/internal/parse.h b/internal/parse.h index d7fc6ddad4bfdb..8e82c0f8974a00 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -68,7 +68,6 @@ rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); -VALUE rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); diff --git a/test/fiber/test_enumerator.rb b/test/fiber/test_enumerator.rb index e9410f925c3b3a..40f7d0172531dc 100644 --- a/test/fiber/test_enumerator.rb +++ b/test/fiber/test_enumerator.rb @@ -42,12 +42,4 @@ def test_read_characters assert_predicate(i, :closed?) assert_predicate(o, :closed?) end - - def enumerator_fiber_is_nonblocking - enumerator = Enumerator.new do |yielder| - yielder << Fiber.current.blocking? - end - - assert_equal(false, enumerator.next) - end end From c4dadfbd47b200b04c59b72ed53948f964ef9cd6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Apr 2024 20:54:00 +0900 Subject: [PATCH 025/135] Fix a typo, missing `P` in `SETPGRP_VOID` --- ext/pty/pty.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 49d92d15d41d47..6a896ce52fb7e6 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -118,10 +118,10 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) (void) setsid(); #else /* HAS_SETSID */ # ifdef HAVE_SETPGRP -# ifdef SETGRP_VOID +# ifdef SETPGRP_VOID if (setpgrp() == -1) ERROR_EXIT("setpgrp()"); -# else /* SETGRP_VOID */ +# else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) ERROR_EXIT("setpgrp()"); { @@ -132,7 +132,7 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) ERROR_EXIT("ioctl(TIOCNOTTY)"); close(i); } -# endif /* SETGRP_VOID */ +# endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ From cccffeff2f2de0b5f655aaa04d935f2eee4b0cd2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 7 Apr 2024 21:16:26 +0900 Subject: [PATCH 026/135] Fix missing variable It seems like no one has tried to compile on a platform without `setsid` for almost a quarter of a century. --- process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process.c b/process.c index 11d5fc999c8aeb..b1f9931f06d477 100644 --- a/process.c +++ b/process.c @@ -5258,7 +5258,7 @@ static rb_pid_t ruby_setsid(void) { rb_pid_t pid; - int ret; + int ret, fd; pid = getpid(); #if defined(SETPGRP_VOID) From 5d1702e01a36e11b183fe29ce10780a9b1a41cf0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 8 Apr 2024 00:49:01 +1200 Subject: [PATCH 027/135] Enumerator should use a non-blocking fiber, change `rb_fiber_new` to be non-blocking by default. (#10481) --- cont.c | 2 +- test/fiber/test_enumerator.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cont.c b/cont.c index c6a94e0709f369..f7a4863f2cd270 100644 --- a/cont.c +++ b/cont.c @@ -2382,7 +2382,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); } VALUE diff --git a/test/fiber/test_enumerator.rb b/test/fiber/test_enumerator.rb index 40f7d0172531dc..e9410f925c3b3a 100644 --- a/test/fiber/test_enumerator.rb +++ b/test/fiber/test_enumerator.rb @@ -42,4 +42,12 @@ def test_read_characters assert_predicate(i, :closed?) assert_predicate(o, :closed?) end + + def enumerator_fiber_is_nonblocking + enumerator = Enumerator.new do |yielder| + yielder << Fiber.current.blocking? + end + + assert_equal(false, enumerator.next) + end end From 4dd9e5cf7447ec70a55206fd5e1b9e8c79dbba7e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 6 Apr 2024 17:45:58 +0900 Subject: [PATCH 028/135] Add builtin type assertion --- include/ruby/assert.h | 11 +++++++++++ string.c | 2 +- symbol.c | 12 ++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/include/ruby/assert.h b/include/ruby/assert.h index ceab090427924a..e9edd9e640a186 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -281,6 +281,17 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) # define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) #endif +/** + * A variant of #RUBY_ASSERT that asserts when either #RUBY_DEBUG or built-in + * type of `obj` is `type`. + * + * @param obj Object to check its built-in typue. + * @param type Built-in type constant, T_ARRAY, T_STRING, etc. + */ +#define RUBY_ASSERT_BUILTIN_TYPE(obj, type) \ + RUBY_ASSERT(RB_TYPE_P(obj, type), \ + "Actual type is %s", rb_builtin_type_name(BUILTIN_TYPE(obj))) + /** * This is either #RUBY_ASSERT or #RBIMPL_ASSUME, depending on #RUBY_DEBUG. * diff --git a/string.c b/string.c index b656f7ab0d0666..6c3a95d47ba2ca 100644 --- a/string.c +++ b/string.c @@ -11764,7 +11764,7 @@ sym_inspect(VALUE sym) } dest[0] = ':'; - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); return str; } diff --git a/symbol.c b/symbol.c index ba28e6650c3ce3..7126154bf8e16d 100644 --- a/symbol.c +++ b/symbol.c @@ -430,8 +430,8 @@ static void set_id_entry(rb_symbols_t *symbols, rb_id_serial_t num, VALUE str, VALUE sym) { ASSERT_vm_locking(); - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); - RUBY_ASSERT(SYMBOL_P(sym)); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(sym, T_SYMBOL); size_t idx = num / ID_ENTRY_UNIT; @@ -484,10 +484,10 @@ get_id_serial_entry(rb_id_serial_t num, ID id, const enum id_entry_type t) if (result) { switch (t) { case ID_ENTRY_STR: - RUBY_ASSERT(BUILTIN_TYPE(result) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(result, T_STRING); break; case ID_ENTRY_SYM: - RUBY_ASSERT(SYMBOL_P(result)); + RUBY_ASSERT_BUILTIN_TYPE(result, T_SYMBOL); break; default: break; @@ -972,11 +972,11 @@ rb_sym2str(VALUE sym) VALUE str; if (DYNAMIC_SYM_P(sym)) { str = RSYMBOL(sym)->fstr; - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); } else { str = rb_id2str(STATIC_SYM2ID(sym)); - RUBY_ASSERT(str == 0 || BUILTIN_TYPE(str) == T_STRING); + if (str) RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); } return str; From 6f7e8e278fefb70af893937e4d005367f6cbbc2b Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Apr 2024 18:51:20 +0900 Subject: [PATCH 029/135] Don't set T_TYPES of NODE T_TYPES was needed once Ripper jumbled NODEs and other type objects. However such hack was already removed. Therefore don't need to set T_TYPES of NODE. --- node.c | 9 +-------- rubyparser.h | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/node.c b/node.c index 8ef7bb7c46a77a..79520f0a1e942c 100644 --- a/node.c +++ b/node.c @@ -15,7 +15,6 @@ #include "node.h" #include "rubyparser.h" #include "internal/parse.h" -#define T_NODE 0x1b #else @@ -87,16 +86,10 @@ rb_node_buffer_new(void) typedef void node_itr_t(rb_ast_t *ast, void *ctx, NODE *node); static void iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, void *ctx); -/* Setup NODE structure. - * NODE is not an object managed by GC, but it imitates an object - * so that it can work with `RB_TYPE_P(obj, T_NODE)`. - * This dirty hack is needed because Ripper jumbles NODEs and other type - * objects. - */ void rb_node_init(NODE *n, enum node_type type) { - RNODE(n)->flags = T_NODE; + RNODE(n)->flags = 0; nd_init_type(RNODE(n), type); RNODE(n)->nd_loc.beg_pos.lineno = 0; RNODE(n)->nd_loc.beg_pos.column = 0; diff --git a/rubyparser.h b/rubyparser.h index f6fafa473fd87e..bbfcd92194d75b 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1178,7 +1178,7 @@ typedef struct RNode_ERROR { #define RNODE_ENCODING(node) ((struct RNode_ENCODING *)(node)) /* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */ -/* NODE_FL: 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: NODE_FL_NEWLINE, +/* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE, * 8..14: nd_type, * 15..: nd_line */ From 2e153fd0378cb99b35b5c81ed259e777dc115392 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 02:59:44 +0000 Subject: [PATCH 030/135] Bump github/codeql-action from 3.24.9 to 3.24.10 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.9 to 3.24.10. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/1b1aada464948af03b950897e5eb522f92603cc2...4355270be187e1b672a7a1c7c7bae5afdc1ab94a) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/scorecards.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 430a2c950d21b6..f203b6228a3ed1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -80,15 +80,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: category: '/language:${{ matrix.language }}' upload: False @@ -118,7 +118,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2889d4d47c4747..69b31e6b698783 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v2.1.27 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v2.1.27 with: sarif_file: results.sarif From 19f4b06b9d9698c97bad5f342324517455be6843 Mon Sep 17 00:00:00 2001 From: Reznov <78517110+alantudyk@users.noreply.github.com> Date: Mon, 8 Apr 2024 06:51:32 +0300 Subject: [PATCH 031/135] Reducing the number of divisions in `rb_fix_digits` --- numeric.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index f0a0e3c279e9d1..f613d4120d7e70 100644 --- a/numeric.c +++ b/numeric.c @@ -5453,11 +5453,12 @@ rb_fix_digits(VALUE fix, long base) return rb_ary_new_from_args(1, INT2FIX(0)); digits = rb_ary_new(); - while (x > 0) { + while (x >= base) { long q = x % base; rb_ary_push(digits, LONG2NUM(q)); x /= base; } + rb_ary_push(digits, LONG2NUM(x)); return digits; } From 76914d474d93b7485973c3bca4fa43b59f5bd383 Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara Date: Mon, 8 Apr 2024 12:41:29 +0900 Subject: [PATCH 032/135] Fix error when default gem is loaded from `-r` option This patch fixes an error when a default gem that will be migrated to a bundled gem is loaded from `-r` option. Problem === `bundle exec ruby -rostruct -e ''` unexpectedly raises the following error: ```console $ ruby -v ruby 3.4.0dev (2024-04-08T02:39:00Z master 6f7e8e278f) [arm64-darwin21] $ bundle init && bundle install $ bundle exec ruby -rostruct -e '' /Users/kuwabara.masataka/.rbenv/versions/trunk/lib/ruby/3.4.0+0/bundled_gems.rb:111:in 'Gem::BUNDLED_GEMS.warning?': undefined method 'find' for nil (NoMethodError) caller = caller_locations(3, 3).find {|c| c&.absolute_path} ^^^^^ from /Users/kuwabara.masataka/.rbenv/versions/trunk/lib/ruby/3.4.0+0/bundled_gems.rb:75:in 'block (2 levels) in Kernel#replace_require' ``` Solution === This patch uses a safe navigation operator to fix this problem. By this change, the command will show the warning message correctly. ```console $ bundle exec ruby -rostruct -e '' warning: ostruct was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.5.0. Add ostruct to your Gemfile or gemspec. ``` --- lib/bundled_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index f3e1708e238a61..e61c1ad231b297 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -108,7 +108,7 @@ def self.warning?(name, specs: nil) _t, path = $:.resolve_feature_path(feature) if gem = find_gem(path) return if specs.include?(gem) - caller = caller_locations(3, 3).find {|c| c&.absolute_path} + caller = caller_locations(3, 3)&.find {|c| c&.absolute_path} return if find_gem(caller&.absolute_path) elsif SINCE[name] && !path gem = true From 76efed65bd905c68c6b3f34d304a30359afae433 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Apr 2024 16:20:15 +0900 Subject: [PATCH 033/135] Added test case for bundled gems warning with -r option. This is for 76914d474d93b7485973c3bca4fa43b59f5bd383 --- tool/test_for_warn_bundled_gems/test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/test_for_warn_bundled_gems/test.sh b/tool/test_for_warn_bundled_gems/test.sh index ef5007f320ab77..2404571daf8880 100755 --- a/tool/test_for_warn_bundled_gems/test.sh +++ b/tool/test_for_warn_bundled_gems/test.sh @@ -24,6 +24,10 @@ echo "* Show warning when bundle exec with shebang's script" bundle exec ./test_warn_bundle_exec_shebang.rb echo +echo "* Show warning when bundle exec with -r option" +bundle exec ruby -rostruct -e '' +echo + echo "* Show warning with bootsnap" ruby test_warn_bootsnap.rb echo From 8217fbf4bd45fa829fde33bc0b0cfabf34eac50b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Apr 2024 18:29:13 +0900 Subject: [PATCH 034/135] [ruby/tmpdir] Display the offending parent path in the exception https://github.com/ruby/tmpdir/commit/7751b12e97 --- lib/tmpdir.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 65c86e9b90e4ef..fe3e0e19d1f00c 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -107,9 +107,10 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options) yield path.dup ensure unless base - stat = File.stat(File.dirname(path)) + base = File.dirname(path) + stat = File.stat(base) if stat.world_writable? and !stat.sticky? - raise ArgumentError, "parent directory is world writable but not sticky" + raise ArgumentError, "parent directory is world writable but not sticky: #{base}" end end FileUtils.remove_entry path From 49b31c7680a86413853d0c2ce2124d3cba56d334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 8 Apr 2024 12:41:55 +0200 Subject: [PATCH 035/135] Document STR_CHILLED flag on RString [Feature #20205] --- string.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/string.c b/string.c index 6c3a95d47ba2ca..040228844da0cb 100644 --- a/string.c +++ b/string.c @@ -87,6 +87,8 @@ VALUE rb_cSymbol; * 2: STR_SHARED (equal to ELTS_SHARED) * The string is shared. The buffer this string points to is owned by * another string (the shared root). + * 3: STR_CHILLED (will be frozen in a future version) + * The string appears frozen but can be mutated with a warning. * 5: STR_SHARED_ROOT * Other strings may point to the contents of this string. When this * flag is set, STR_SHARED must not be set. From 00cbdb5a8be7af849874ecdc5ef8a46eed4b214b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Apr 2024 21:26:11 +0900 Subject: [PATCH 036/135] Skip even "Document" only --- .github/workflows/annocheck.yml | 6 +++--- .github/workflows/baseruby.yml | 6 +++--- .github/workflows/check_dependencies.yml | 6 +++--- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/compilers.yml | 6 +++--- .github/workflows/macos.yml | 6 +++--- .github/workflows/mingw.yml | 6 +++--- .github/workflows/prism.yml | 6 +++--- .github/workflows/rjit-bindgen.yml | 6 +++--- .github/workflows/rjit.yml | 6 +++--- .github/workflows/spec_guards.yml | 6 +++--- .github/workflows/ubuntu.yml | 6 +++--- .github/workflows/wasm.yml | 6 +++--- .github/workflows/windows.yml | 6 +++--- .github/workflows/yjit-macos.yml | 12 ++++++------ .github/workflows/yjit-ubuntu.yml | 10 +++++----- 16 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 175b31a2bb2923..bc5b81a9b99b2c 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -39,10 +39,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 36962338f9114a..2bc1e39e0c807a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -35,10 +35,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 153d456e89db35..5187138be97c8e 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -37,10 +37,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f203b6228a3ed1..38148d4f667ce6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,10 +41,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 4b58d9d10a1cb6..0eebb0eea21939 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -220,10 +220,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c89635ea822430..87231228bcef83 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -44,10 +44,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 7b9db05f116832..1ed83de53521a3 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -57,10 +57,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 7fc10e2ab12775..adb9206c369178 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -47,10 +47,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/rjit-bindgen.yml b/.github/workflows/rjit-bindgen.yml index eea5a0c0151dd7..65f7b080a3d678 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -38,10 +38,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/rjit.yml b/.github/workflows/rjit.yml index 7382c1179f0d23..b393074919ad11 100644 --- a/.github/workflows/rjit.yml +++ b/.github/workflows/rjit.yml @@ -47,10 +47,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index c14b4ee03845fd..f4d8c378373eb0 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -27,10 +27,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 62e1b564b8828b..b4bcb8223a08fc 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -48,10 +48,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index ad7ea13cbf3e45..5ec4bfafaed22e 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -53,10 +53,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index db2235eb6c972a..56e2711b5b9d5d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -42,10 +42,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index be3539c2ea8c6a..5e209cc4cae5a1 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -29,10 +29,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -71,10 +71,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index b74629340f0a7e..92ff9eadaed607 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -31,7 +31,7 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -63,7 +63,7 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -117,10 +117,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} From 478b4ef9de0de0d087f5c8d4d94b0b4af3753c6f Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 8 Apr 2024 12:31:33 -0400 Subject: [PATCH 037/135] Fix nil error with KNOWNBUGS.rb Previously, `make test-knownbugs` crashed with `NoMethodError` due to the failed regex match if there is a test case in KNOWNBUGS.rb. The note about 1.8 compatibility is probably bogus as we require a way more recent BASERUBY now. --- bootstraptest/runner.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 2135f1427656d1..20f121cdf492f0 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -6,7 +6,6 @@ # Never use optparse in this file. # Never use test/unit in this file. # Never use Ruby extensions in this file. -# Maintain Ruby 1.8 compatibility for now $start_time = Time.now @@ -428,7 +427,7 @@ def add as def initialize(*args) super self.class.add self - @category = self.path.match(/test_(.+)\.rb/)[1] + @category = self.path[/\Atest_(.+)\.rb\z/, 1] end def call From b09604e1fd5768daf31aaa4f8130fa8cd9b8d240 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 3 Apr 2024 14:36:56 -0400 Subject: [PATCH 038/135] [ruby/prism] Simplify locals test now that all fixtures are valid https://github.com/ruby/prism/commit/0d32af5719 --- test/prism/fixtures/if.txt | 4 ++-- test/prism/locals_test.rb | 46 +++---------------------------------- test/prism/snapshots/if.txt | 26 ++++++++++++++++----- 3 files changed, 25 insertions(+), 51 deletions(-) diff --git a/test/prism/fixtures/if.txt b/test/prism/fixtures/if.txt index cede644a4c2149..4139bae5edbbd2 100644 --- a/test/prism/fixtures/if.txt +++ b/test/prism/fixtures/if.txt @@ -30,10 +30,10 @@ if type in 1 elsif type in B end -if 1 +if f1 lambda do |_| end -elsif 2 +elsif f2 lambda do |_| end else diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 1453e7aecddedd..0eb73f1b9c76b0 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -17,51 +17,11 @@ module Prism class LocalsTest < TestCase - invalid = [] - todos = [] - - # Invalid break - invalid << "break.txt" - invalid << "if.txt" - invalid << "rescue.txt" - invalid << "seattlerb/block_break.txt" - invalid << "unless.txt" - invalid << "whitequark/break.txt" - invalid << "whitequark/break_block.txt" - - # Invalid next - invalid << "next.txt" - invalid << "seattlerb/block_next.txt" - invalid << "unparser/corpus/literal/control.txt" - invalid << "whitequark/next.txt" - invalid << "whitequark/next_block.txt" - - # Invalid redo - invalid << "keywords.txt" - invalid << "whitequark/redo.txt" - - # Invalid retry - invalid << "whitequark/retry.txt" - - # Invalid yield - invalid << "seattlerb/dasgn_icky2.txt" - invalid << "seattlerb/yield_arg.txt" - invalid << "seattlerb/yield_call_assocs.txt" - invalid << "seattlerb/yield_empty_parens.txt" - invalid << "unparser/corpus/literal/yield.txt" - invalid << "whitequark/args_assocs.txt" - invalid << "whitequark/args_assocs_legacy.txt" - invalid << "whitequark/yield.txt" - invalid << "yield.txt" - - # Dead code eliminated - invalid << "whitequark/ruby_bug_10653.txt" - base = File.join(__dir__, "fixtures") - skips = invalid | todos - Dir["**/*.txt", base: base].each do |relative| - next if skips.include?(relative) + # Skip this fixture because it has a different number of locals because + # CRuby is eliminating dead code. + next if relative == "whitequark/ruby_bug_10653.txt" filepath = File.join(base, relative) define_method("test_#{relative}") { assert_locals(filepath) } diff --git a/test/prism/snapshots/if.txt b/test/prism/snapshots/if.txt index b618659756a215..eb33d1699d55f8 100644 --- a/test/prism/snapshots/if.txt +++ b/test/prism/snapshots/if.txt @@ -391,9 +391,16 @@ └── @ IfNode (location: (33,0)-(42,3)) ├── if_keyword_loc: (33,0)-(33,2) = "if" ├── predicate: - │ @ IntegerNode (location: (33,3)-(33,4)) - │ ├── flags: decimal - │ └── value: 1 + │ @ CallNode (location: (33,3)-(33,5)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :f1 + │ ├── message_loc: (33,3)-(33,5) = "f1" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ └── block: ∅ ├── then_keyword_loc: ∅ ├── statements: │ @ StatementsNode (location: (34,2)-(35,5)) @@ -434,9 +441,16 @@ │ @ IfNode (location: (36,0)-(42,3)) │ ├── if_keyword_loc: (36,0)-(36,5) = "elsif" │ ├── predicate: - │ │ @ IntegerNode (location: (36,6)-(36,7)) - │ │ ├── flags: decimal - │ │ └── value: 2 + │ │ @ CallNode (location: (36,6)-(36,8)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :f2 + │ │ ├── message_loc: (36,6)-(36,8) = "f2" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ │ ├── then_keyword_loc: ∅ │ ├── statements: │ │ @ StatementsNode (location: (37,2)-(38,5)) From 186374279e5c42cee2969496df453766eb984d96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:11:59 +0000 Subject: [PATCH 039/135] [rubygems/rubygems] Bump rb-sys Bumps [rb-sys](https://github.com/oxidize-rb/rb-sys) from 0.9.90 to 0.9.91. - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.90...v0.9.91) --- updated-dependencies: - dependency-name: rb-sys dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/f0af491002 --- .../custom_name/ext/custom_name_lib/Cargo.lock | 8 ++++---- .../custom_name/ext/custom_name_lib/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index bc76ee824e577f..34db31f61c81f4 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d933382388cc7a6fdfd54e222eca7994791ac4b9ce5c9e8df280c739d86bbe" +checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc5a7e3a875419baaa0d8cc606cdfb9361b444cb7e5abcf0de4693025887374" +checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 424c61e45226cc..00a48df5d57ddb 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.90" +rb-sys = "0.9.91" From c924322162ae2bbe79a4aba7c7e85ecb8293dd7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:07:50 +0000 Subject: [PATCH 040/135] [rubygems/rubygems] Bump rb-sys Bumps [rb-sys](https://github.com/oxidize-rb/rb-sys) from 0.9.90 to 0.9.91. - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.90...v0.9.91) --- updated-dependencies: - dependency-name: rb-sys dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/a84b94b9ec --- .../rust_ruby_example/Cargo.lock | 8 ++++---- .../rust_ruby_example/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 612196063f3ffa..f96b1442936e2f 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d933382388cc7a6fdfd54e222eca7994791ac4b9ce5c9e8df280c739d86bbe" +checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc5a7e3a875419baaa0d8cc606cdfb9361b444cb7e5abcf0de4693025887374" +checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index e584a7f427e1b2..dca81463949243 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.90" +rb-sys = "0.9.91" From 3d73cd752f5dbdff4c77c57d011ca7f8d783c8ba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 5 Apr 2024 18:53:15 +0900 Subject: [PATCH 041/135] Hack to update spec/bundler/support/builders.rb --- .github/workflows/bundled_gems.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 3b7bfbb3039183..96bca3e3aa5573 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -56,6 +56,13 @@ jobs: run: | ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT + - name: Update spec/bundler/support/builders.rb + run: | + #!ruby + rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] + print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} + shell: ruby -i~ {0} spec/bundler/support/builders.rb + - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled @@ -69,6 +76,7 @@ jobs: git diff --color --no-ext-diff --ignore-submodules --exit-code -- gems/bundled_gems || gems=true git add -- NEWS.md gems/bundled_gems + git add -- spec/bundler/support/builders.rb echo news=$news >> $GITHUB_OUTPUT echo gems=$gems >> $GITHUB_OUTPUT echo update=${news:-$gems} >> $GITHUB_OUTPUT From 4dbd9c7fec18fe9dcde8aeb58d7df7997fc8b266 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Tue, 9 Apr 2024 08:13:19 +0900 Subject: [PATCH 042/135] Remove unused function from `struct rb_parser_config_struct` --- ruby_parser.c | 1 - rubyparser.h | 1 - universal_parser.c | 2 -- 3 files changed, 4 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index 76dfa3b7be4203..2d914e865b398a 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -435,7 +435,6 @@ static const rb_parser_config_t rb_global_parser_config = { .ary_push = rb_ary_push, .ary_new_from_args = rb_ary_new_from_args, .ary_unshift = rb_ary_unshift, - .ary_new2 = rb_ary_new2, .ary_modify = rb_ary_modify, .array_len = rb_array_len, .array_aref = RARRAY_AREF, diff --git a/rubyparser.h b/rubyparser.h index bbfcd92194d75b..268e8deeff6f41 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1256,7 +1256,6 @@ typedef struct rb_parser_config_struct { VALUE (*ary_push)(VALUE ary, VALUE elem); VALUE (*ary_new_from_args)(long n, ...); VALUE (*ary_unshift)(VALUE ary, VALUE item); - VALUE (*ary_new2)(long capa); // ary_new_capa void (*ary_modify)(VALUE ary); long (*array_len)(VALUE a); VALUE (*array_aref)(VALUE, long); diff --git a/universal_parser.c b/universal_parser.c index b8b1cb9f846a48..ecbc242f08ebb1 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -95,8 +95,6 @@ #undef rb_ary_new_from_args #define rb_ary_new_from_args p->config->ary_new_from_args #define rb_ary_unshift p->config->ary_unshift -#undef rb_ary_new2 -#define rb_ary_new2 p->config->ary_new2 #define rb_ary_modify p->config->ary_modify #undef RARRAY_LEN #define RARRAY_LEN p->config->array_len From 6846b985760d104320cc7c111754f4d26d159f63 Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Tue, 9 Apr 2024 22:47:19 +0900 Subject: [PATCH 043/135] [ruby/reline] Bump version to 0.5.1 (https://github.com/ruby/reline/pull/672) https://github.com/ruby/reline/commit/d348df90d2 --- lib/reline/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 06bb3acc8454e9..acb5e279b8cc77 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.5.0' + VERSION = '0.5.1' end From 88355da67303c57729381e0af969994658e494b3 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 9 Apr 2024 13:48:19 +0000 Subject: [PATCH 044/135] Update default gems list at 6846b985760d104320cc7c111754f4 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 8ba955d48e90d4..18c030909c5d56 100644 --- a/NEWS.md +++ b/NEWS.md @@ -47,7 +47,7 @@ The following default gems are updated. * net-http 0.4.1 * prism 0.25.0 * rdoc 6.6.3.1 -* reline 0.5.0 +* reline 0.5.1 * resolv 0.4.0 * stringio 3.1.1 * strscan 3.1.1 From 0bc71828b596c763f09d674260f97722a311e764 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Apr 2024 11:55:26 +0900 Subject: [PATCH 045/135] [pty] Split `chfunc` into functions in steps - start a new session - obtain the new controlling terminal - drop privileges - finally, `exec` --- ext/pty/pty.c | 53 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 6a896ce52fb7e6..7667485579ec56 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -92,9 +92,13 @@ struct pty_info { static void getDevice(int*, int*, char [DEVICELEN], int); +static int start_new_session(char *errbuf, size_t errbuf_len); +static int obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len); +static int drop_privilige(char *errbuf, size_t errbuf_len); + struct child_info { int master, slave; - char *slavename; + const char *slavename; VALUE execarg_obj; struct rb_execarg *eargp; }; @@ -102,18 +106,34 @@ struct child_info { static int chfunc(void *data, char *errbuf, size_t errbuf_len) { - struct child_info *carg = data; + const struct child_info *carg = data; int master = carg->master; int slave = carg->slave; + const char *slavename = carg->slavename; + + if (start_new_session(errbuf, errbuf_len)) + return -1; + + if (obtain_ctty(master, slave, slavename, errbuf, errbuf_len)) + return -1; + + if (drop_privilige(errbuf, errbuf_len)) + return -1; + + return rb_exec_async_signal_safe(carg->eargp, errbuf, errbuf_len); +} #define ERROR_EXIT(str) do { \ strlcpy(errbuf, (str), errbuf_len); \ return -1; \ } while (0) - /* - * Set free from process group and controlling terminal - */ +/* + * Set free from process group and controlling terminal + */ +static int +start_new_session(char *errbuf, size_t errbuf_len) +{ #ifdef HAVE_SETSID (void) setsid(); #else /* HAS_SETSID */ @@ -135,17 +155,22 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) # endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ + return 0; +} - /* - * obtain new controlling terminal - */ +/* + * obtain new controlling terminal + */ +static int +obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len) +{ #if defined(TIOCSCTTY) close(master); (void) ioctl(slave, TIOCSCTTY, (char *)0); /* errors ignored for sun */ #else close(slave); - slave = rb_cloexec_open(carg->slavename, O_RDWR, 0); + slave = rb_cloexec_open(slavename, O_RDWR, 0); if (slave < 0) { ERROR_EXIT("open: pty slave"); } @@ -156,13 +181,19 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) dup2(slave,1); dup2(slave,2); if (slave < 0 || slave > 2) (void)!close(slave); + return 0; +} + +static int +drop_privilige(char *errbuf, size_t errbuf_len) +{ #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID) if (seteuid(getuid())) ERROR_EXIT("seteuid()"); #endif + return 0; +} - return rb_exec_async_signal_safe(carg->eargp, errbuf, sizeof(errbuf_len)); #undef ERROR_EXIT -} static void establishShell(int argc, VALUE *argv, struct pty_info *info, From d101ec65e9507310b5498ffd8ced4c5a6624662c Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 9 Apr 2024 11:30:41 -0400 Subject: [PATCH 046/135] [ruby/prism] Reduce locals variables per CRuby https://github.com/ruby/prism/commit/3e6830c3a5 --- prism/prism.c | 106 +++++++++++++++++++++++++++++------- test/prism/warnings_test.rb | 45 +++++++++++++-- 2 files changed, 127 insertions(+), 24 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 086716e92bf581..c394ae95b6cb4e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1005,7 +1005,7 @@ pm_locals_reads(pm_locals_t *locals, pm_constant_id_t name) { * written but not read in certain contexts. */ static void -pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool warn_unused) { +pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool toplevel) { pm_constant_id_list_init_capacity(list, locals->size); // If we're still below the threshold for switching to a hash, then we only @@ -1013,6 +1013,10 @@ pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, // stored in a list. uint32_t capacity = locals->capacity < PM_LOCALS_HASH_THRESHOLD ? locals->size : locals->capacity; + // We will only warn for unused variables if we're not at the top level, or + // if we're parsing a file outside of eval or -e. + bool warn_unused = !toplevel || (!parser->parsing_eval && !PM_PARSER_COMMAND_LINE_OPTION_E(parser)); + for (uint32_t index = 0; index < capacity; index++) { pm_local_t *local = &locals->locals[index]; @@ -12329,7 +12333,8 @@ static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id); /** - * This is a wrapper of parse_expression, which also checks whether the resulting node is value expression. + * This is a wrapper of parse_expression, which also checks whether the + * resulting node is a value expression. */ static pm_node_t * parse_value_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { @@ -13217,7 +13222,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_static_literals_t literals = { 0 }; pm_hash_key_static_literals_add(parser, &literals, argument); - // Finish parsing the one we are part way through + // Finish parsing the one we are part way through. pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_VALUE); argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value); @@ -14014,9 +14019,8 @@ parse_block_parameters( pm_parser_local_add_token(parser, &parser->previous, 1); pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); - if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)local); - } + if (repeated) pm_node_flag_set_repeated_parameter((pm_node_t *) local); + pm_block_parameters_node_append_local(block_parameters, local); } while (accept1(parser, PM_TOKEN_COMMA)); } @@ -14118,7 +14122,7 @@ parse_block(pm_parser_t *parser) { } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser)); + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous); pm_parser_scope_pop(parser); @@ -17442,7 +17446,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); @@ -17502,7 +17506,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); @@ -17767,7 +17771,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); /** @@ -18029,7 +18033,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM); @@ -18795,7 +18799,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser)); + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous); pm_parser_scope_pop(parser); @@ -18851,11 +18855,21 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } -static inline pm_node_t * +/** + * Parse a value that is going to be written to some kind of variable or method + * call. We need to handle this separately because the rescue modifier is + * permitted on the end of the these expressions, which is a deviation from its + * normal binding power. + * + * Note that this will only be called after an operator write, as in &&=, ||=, + * or any of the binary operators that can be written to a variable. + */ +static pm_node_t * parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_value_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - // Contradicting binding powers, the right-hand-side value of rthe assignment allows the `rescue` modifier. + // Contradicting binding powers, the right-hand-side value of the assignment + // allows the `rescue` modifier. if (match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); @@ -18871,14 +18885,63 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ return value; } +/** + * When a local variable write node is the value being written in a different + * write, the local variable is considered "used". + */ +static void +parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_BEGIN_NODE: { + const pm_begin_node_t *cast = (const pm_begin_node_t *) node; + if (cast->statements != NULL) parse_assignment_value_local(parser, (const pm_node_t *) cast->statements); + break; + } + case PM_LOCAL_VARIABLE_WRITE_NODE: { + const pm_local_variable_write_node_t *cast = (const pm_local_variable_write_node_t *) node; + pm_locals_read(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name); + break; + } + case PM_PARENTHESES_NODE: { + const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; + if (cast->body != NULL) parse_assignment_value_local(parser, cast->body); + break; + } + case PM_STATEMENTS_NODE: { + const pm_statements_node_t *cast = (const pm_statements_node_t *) node; + const pm_node_t *statement; -static inline pm_node_t * + PM_NODE_LIST_FOREACH(&cast->body, index, statement) { + parse_assignment_value_local(parser, statement); + } + break; + } + default: + break; + } +} + +/** + * Parse the value (or values, through an implicit array) that is going to be + * written to some kind of variable or method call. We need to handle this + * separately because the rescue modifier is permitted on the end of the these + * expressions, which is a deviation from its normal binding power. + * + * Additionally, if the value is a local variable write node (e.g., a = a = 1), + * the "a" is marked as being used so the parser should not warn on it. + * + * Note that this will only be called after an = operator, as that is the only + * operator that allows multiple values after it. + */ +static pm_node_t * parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - bool single_value = true; + parse_assignment_value_local(parser, value); + bool single_value = true; if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { single_value = false; + pm_token_t opening = not_provided(parser); pm_array_node_t *array = pm_array_node_create(parser, &opening); @@ -18887,8 +18950,11 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding while (accept1(parser, PM_TOKEN_COMMA)) { pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT); + pm_array_node_elements_append(array, element); if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break; + + parse_assignment_value_local(parser, element); } } @@ -19173,7 +19239,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -19286,7 +19352,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -19409,7 +19475,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -20070,7 +20136,7 @@ parse_program(pm_parser_t *parser) { } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, false); + pm_locals_order(parser, &parser->current_scope->locals, &locals, true); pm_parser_scope_pop(parser); // If this is an empty file, then we're still going to parse all of the diff --git a/test/prism/warnings_test.rb b/test/prism/warnings_test.rb index 4da2af626a29a4..5f1e746b525927 100644 --- a/test/prism/warnings_test.rb +++ b/test/prism/warnings_test.rb @@ -24,7 +24,7 @@ def test_ambiguous_regexp end def test_equal_in_conditional - assert_warning("if a = 1; end", "should be ==") + assert_warning("if a = 1; end; a", "should be ==") end def test_dot_dot_dot_eol @@ -88,6 +88,43 @@ def test_regexp_in_predicate assert_warning("if /foo\#{bar}/; end", "regex") end + def test_unused_local_variables + assert_warning("foo = 1", "unused") + + refute_warning("foo = 1", compare: false, command_line: "e") + refute_warning("foo = 1", compare: false, scopes: [[]]) + + assert_warning("def foo; bar = 1; end", "unused") + assert_warning("def foo; bar, = 1; end", "unused") + + refute_warning("def foo; bar &&= 1; end") + refute_warning("def foo; bar ||= 1; end") + refute_warning("def foo; bar += 1; end") + + refute_warning("def foo; bar = bar; end") + refute_warning("def foo; bar = bar = 1; end") + refute_warning("def foo; bar = (bar = 1); end") + refute_warning("def foo; bar = begin; bar = 1; end; end") + refute_warning("def foo; bar = (qux; bar = 1); end") + refute_warning("def foo; bar, = bar = 1; end") + refute_warning("def foo; bar, = 1, bar = 1; end") + + refute_warning("def foo(bar); end") + refute_warning("def foo(bar = 1); end") + refute_warning("def foo((bar)); end") + refute_warning("def foo(*bar); end") + refute_warning("def foo(*, bar); end") + refute_warning("def foo(*, (bar)); end") + refute_warning("def foo(bar:); end") + refute_warning("def foo(**bar); end") + refute_warning("def foo(&bar); end") + refute_warning("->(bar) {}") + refute_warning("->(; bar) {}", compare: false) + + refute_warning("def foo; bar = 1; tap { bar }; end") + refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end") + end + private def assert_warning(source, message) @@ -101,10 +138,10 @@ def assert_warning(source, message) end end - def refute_warning(source) - assert_empty Prism.parse(source).warnings + def refute_warning(source, compare: true, **options) + assert_empty Prism.parse(source, **options).warnings - if defined?(RubyVM::AbstractSyntaxTree) + if compare && defined?(RubyVM::AbstractSyntaxTree) assert_empty capture_warning { RubyVM::AbstractSyntaxTree.parse(source) } end end From 0107954f257af6fd4cb280aa36b9b320795c0a86 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 9 Apr 2024 12:23:27 -0400 Subject: [PATCH 047/135] [ruby/prism] Fix up invalid global variable error message https://github.com/ruby/prism/commit/8ce9ae487f --- prism/prism.c | 4 ++++ test/prism/errors_test.rb | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index c394ae95b6cb4e..16dad45b37b7e9 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8404,6 +8404,10 @@ lex_global_variable(pm_parser_t *parser) { do { parser->current.end += width; } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0); + } else if (pm_char_is_whitespace(peek(parser))) { + // If we get here, then we have a $ followed by whitespace, + // which is not allowed. + pm_parser_err_token(parser, &parser->current, PM_ERR_GLOBAL_VARIABLE_BARE); } else { // If we get here, then we have a $ followed by something that // isn't recognized as a global variable. diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 79a38b41d068ed..cabc96c8eabdae 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -1257,9 +1257,10 @@ def test_invalid_operator_write_dot end def test_unterminated_global_variable - assert_errors expression("$"), "$", [ - ["'$' without identifiers is not allowed as a global variable name", 0..1] - ] + message = "'$' without identifiers is not allowed as a global variable name" + + assert_errors expression("$"), "$", [[message, 0..1]] + assert_errors expression("$ "), "$ ", [[message, 0..1]] end def test_invalid_global_variable_write From 13f04e5beb801d17fce6aa3bc36f6da7a0953874 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Apr 2024 18:03:12 +0900 Subject: [PATCH 048/135] [ruby/io-console] Load the built extension library in noctty tests https://github.com/ruby/io-console/commit/74c78afc24 --- test/io/console/test_io_console.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 40551286d0397b..0a113ebd2f82d8 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -15,6 +15,7 @@ class TestIO_Console < Test::Unit::TestCase raise end PATHS.uniq! + INCLUDE_OPTS = "-I#{PATHS.join(File::PATH_SEPARATOR)}" # FreeBSD seems to hang on TTOU when running parallel tests # tested on FreeBSD 11.x. @@ -457,7 +458,7 @@ def helper def run_pty(src, n = 1) pend("PTY.spawn cannot control terminal on JRuby") if RUBY_ENGINE == 'jruby' - args = ["-I#{TestIO_Console::PATHS.join(File::PATH_SEPARATOR)}", "-rio/console", "-e", src] + args = [TestIO_Console::INCLUDE_OPTS, "-rio/console", "-e", src] args.shift if args.first == "-I" # statically linked r, w, pid = PTY.spawn(EnvUtil.rubybin, *args) rescue RuntimeError @@ -551,6 +552,7 @@ def test_noctty t2 = Tempfile.new("noctty_run") t2.close cmd = [*NOCTTY[1..-1], + TestIO_Console::INCLUDE_OPTS, '-e', 'open(ARGV[0], "w") {|f|', '-e', 'STDOUT.reopen(f)', '-e', 'STDERR.reopen(f)', From f9f25d0ed001ae7c7a335c32fb3c5f0895709b9b Mon Sep 17 00:00:00 2001 From: Taketo Takashima Date: Tue, 9 Aug 2022 00:50:44 +0900 Subject: [PATCH 049/135] [ruby/ipaddr] Added IPAddr#wildcard_mask https://github.com/ruby/ipaddr/commit/2093cebc1d --- lib/ipaddr.rb | 14 ++++++++++++++ test/test_ipaddr.rb | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a6734455321f64..c1073ecd2bc4e1 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -471,6 +471,20 @@ def netmask _to_string(@mask_addr) end + # Returns the wildcard mask in string format e.g. 0.0.255.255 + def wildcard_mask + case @family + when Socket::AF_INET + mask = IN4MASK ^ @mask_addr + when Socket::AF_INET6 + mask = IN6MASK ^ @mask_addr + else + raise AddressFamilyError, "unsupported address family" + end + + _to_string(mask) + end + # Returns the IPv6 zone identifier, if present. # Raises InvalidAddressError if not an IPv6 address. def zone_id diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 4829d9620cd202..e95b0d2d7a0086 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -263,6 +263,29 @@ def test_netmask assert_equal(a.netmask, "255.255.255.0") end + def test_wildcard_mask + a = IPAddr.new("192.168.1.2/1") + assert_equal(a.wildcard_mask, "127.255.255.255") + + a = IPAddr.new("192.168.1.2/8") + assert_equal(a.wildcard_mask, "0.255.255.255") + + a = IPAddr.new("192.168.1.2/16") + assert_equal(a.wildcard_mask, "0.0.255.255") + + a = IPAddr.new("192.168.1.2/24") + assert_equal(a.wildcard_mask, "0.0.0.255") + + a = IPAddr.new("192.168.1.2/32") + assert_equal(a.wildcard_mask, "0.0.0.0") + + a = IPAddr.new("3ffe:505:2::/48") + assert_equal(a.wildcard_mask, "0000:0000:0000:ffff:ffff:ffff:ffff:ffff") + + a = IPAddr.new("3ffe:505:2::/128") + assert_equal(a.wildcard_mask, "0000:0000:0000:0000:0000:0000:0000:0000") + end + def test_zone_id a = IPAddr.new("192.168.1.2") assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%ab0' } From 9f6deaa6888a423720b4b127b5314f0ad26cc2e6 Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Fri, 13 Oct 2023 15:02:23 +0900 Subject: [PATCH 050/135] [Misc #18984] Raise TypeError from Range#size if the range is not iterable --- range.c | 18 ++++++--- spec/ruby/core/range/size_spec.rb | 62 ++++++++++++++++++++++++------- test/ruby/test_range.rb | 50 +++++++++++++++---------- 3 files changed, 92 insertions(+), 38 deletions(-) diff --git a/range.c b/range.c index e9073e53c40eb3..a6bf0fca51eb35 100644 --- a/range.c +++ b/range.c @@ -827,7 +827,12 @@ sym_each_i(VALUE v, VALUE arg) * (1..4).size # => 4 * (1...4).size # => 3 * (1..).size # => Infinity - * ('a'..'z').size #=> nil + * ('a'..'z').size # => nil + * + * If +self+ is not iterable, raises an exception: + * + * (0.5..2.5).size # TypeError + * (..1).size # TypeError * * Related: Range#count. */ @@ -836,7 +841,8 @@ static VALUE range_size(VALUE range) { VALUE b = RANGE_BEG(range), e = RANGE_END(range); - if (rb_obj_is_kind_of(b, rb_cNumeric)) { + + if (RB_INTEGER_TYPE_P(b)) { if (rb_obj_is_kind_of(e, rb_cNumeric)) { return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range)); } @@ -844,10 +850,10 @@ range_size(VALUE range) return DBL2NUM(HUGE_VAL); } } - else if (NIL_P(b)) { - if (rb_obj_is_kind_of(e, rb_cNumeric)) { - return DBL2NUM(HUGE_VAL); - } + + if (!discrete_object_p(b)) { + rb_raise(rb_eTypeError, "can't iterate from %s", + rb_obj_classname(b)); } return Qnil; diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 81ea5a3846ab37..a1fe3ce17de3d7 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -4,34 +4,22 @@ it "returns the number of elements in the range" do (1..16).size.should == 16 (1...16).size.should == 15 - - (1.0..16.0).size.should == 16 - (1.0...16.0).size.should == 15 - (1.0..15.9).size.should == 15 - (1.1..16.0).size.should == 15 - (1.1..15.9).size.should == 15 end it "returns 0 if last is less than first" do (16..0).size.should == 0 - (16.0..0.0).size.should == 0 - (Float::INFINITY..0).size.should == 0 end it 'returns Float::INFINITY for increasing, infinite ranges' do (0..Float::INFINITY).size.should == Float::INFINITY - (-Float::INFINITY..0).size.should == Float::INFINITY - (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY end it 'returns Float::INFINITY for endless ranges if the start is numeric' do eval("(1..)").size.should == Float::INFINITY - eval("(0.5...)").size.should == Float::INFINITY end it 'returns nil for endless ranges if the start is not numeric' do eval("('z'..)").size.should == nil - eval("([]...)").size.should == nil end ruby_version_is ""..."3.2" do @@ -43,7 +31,7 @@ end end - ruby_version_is "3.2" do + ruby_version_is "3.2"..."3.4" do it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY @@ -58,6 +46,54 @@ end end + ruby_version_is ""..."3.4" do + it "returns the number of elements in the range" do + (1.0..16.0).size.should == 16 + (1.0...16.0).size.should == 15 + (1.0..15.9).size.should == 15 + (1.1..16.0).size.should == 15 + (1.1..15.9).size.should == 15 + end + + it "returns 0 if last is less than first" do + (16.0..0.0).size.should == 0 + (Float::INFINITY..0).size.should == 0 + end + + it 'returns Float::INFINITY for increasing, infinite ranges' do + (-Float::INFINITY..0).size.should == Float::INFINITY + (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY + end + + it 'returns Float::INFINITY for endless ranges if the start is numeric' do + eval("(0.5...)").size.should == Float::INFINITY + end + + it 'returns nil for endless ranges if the start is not numeric' do + eval("([]...)").size.should == nil + end + end + + ruby_version_is "3.4" do + it 'raises TypeError if a range is not iterable' do + -> { (1.0..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0...16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (16.0..0.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..Float::INFINITY).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..1).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...0.5).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..nil).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...'o').size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("(0.5...)").size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("([]...)").size }.should raise_error(TypeError, /can't iterate from/) + end + end + it "returns nil if first and last are not Numeric" do (:a..:z).size.should be_nil ('a'..'z').size.should be_nil diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index 2aa69dc6a49e47..84b3b205f03343 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -983,26 +983,38 @@ def test_comparison_when_recursive end def test_size - assert_equal 42, (1..42).size - assert_equal 41, (1...42).size - assert_equal 6, (1...6.3).size - assert_equal 5, (1.1...6).size - assert_equal 3, (1..3r).size - assert_equal 2, (1...3r).size - assert_equal 3, (1..3.1r).size - assert_equal 3, (1...3.1r).size - assert_equal 42, (1..42).each.size + Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end| + r = Range.new(beg.send(m1), ende.send(m2), exclude_end) + iterable = true + yielded = [] + begin + r.each { yielded << _1 } + rescue TypeError + iterable = false + end + + if iterable + assert_equal(yielded.size, r.size, "failed on #{r}") + assert_equal(yielded.size, r.each.size, "failed on #{r}") + else + assert_raise(TypeError, "failed on #{r}") { r.size } + assert_raise(TypeError, "failed on #{r}") { r.each.size } + end + end + assert_nil ("a"..."z").size - assert_nil ("a"...).size - assert_nil (..."z").size # [Bug #18983] - assert_nil (nil...nil).size # [Bug #18983] - - assert_equal Float::INFINITY, (1...).size - assert_equal Float::INFINITY, (1.0...).size - assert_equal Float::INFINITY, (...1).size - assert_equal Float::INFINITY, (...1.0).size - assert_nil ("a"...).size - assert_nil (..."z").size + + assert_equal Float::INFINITY, (1..).size + assert_raise(TypeError) { (1.0..).size } + assert_raise(TypeError) { (1r..).size } + assert_nil ("a"..).size + + assert_raise(TypeError) { (..1).size } + assert_raise(TypeError) { (..1.0).size } + assert_raise(TypeError) { (..1r).size } + assert_raise(TypeError) { (..'z').size } + + assert_raise(TypeError) { (nil...nil).size } end def test_bsearch_typechecks_return_values From 6a505d1b59cf326a8e004fc06e02f30222b17f3f Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 11 Apr 2024 01:52:47 +0900 Subject: [PATCH 051/135] [ruby/irb] Command implementation not by method (https://github.com/ruby/irb/pull/824) * Command is not a method * Fix command test * Implement non-method command name completion * Add test for ExtendCommandBundle.def_extend_command * Add helper method install test * Remove spaces in command input parse * Remove command arg unquote in help command * Simplify Statement and handle execution in IRB::Irb * Tweak require, const name * Always install CommandBundle module to main object * Remove considering local variable in command or expression check * Remove unused method, tweak * Remove outdated comment for help command arg Co-authored-by: Stan Lo --------- https://github.com/ruby/irb/commit/8fb776e379 Co-authored-by: Stan Lo --- lib/irb.rb | 58 ++++++++----- lib/irb/command.rb | 118 +++++++------------------- lib/irb/command/backtrace.rb | 8 +- lib/irb/command/base.rb | 35 ++++++-- lib/irb/command/break.rb | 8 +- lib/irb/command/catch.rb | 8 +- lib/irb/command/chws.rb | 11 ++- lib/irb/command/context.rb | 16 ++++ lib/irb/command/continue.rb | 4 +- lib/irb/command/debug.rb | 6 +- lib/irb/command/delete.rb | 4 +- lib/irb/command/edit.rb | 16 +--- lib/irb/command/finish.rb | 4 +- lib/irb/command/help.rb | 19 +---- lib/irb/command/history.rb | 10 +-- lib/irb/command/info.rb | 8 +- lib/irb/command/irb_info.rb | 2 +- lib/irb/command/load.rb | 22 ++++- lib/irb/command/ls.rb | 28 ++++--- lib/irb/command/measure.rb | 16 ++-- lib/irb/command/next.rb | 4 +- lib/irb/command/pushws.rb | 15 ++-- lib/irb/command/show_doc.rb | 27 ++---- lib/irb/command/show_source.rb | 15 +--- lib/irb/command/step.rb | 4 +- lib/irb/command/subirb.rb | 35 +++++--- lib/irb/command/whereami.rb | 2 +- lib/irb/completion.rb | 17 +++- lib/irb/context.rb | 12 --- lib/irb/ext/change-ws.rb | 8 +- lib/irb/ext/workspaces.rb | 7 +- lib/irb/statement.rb | 32 ++----- lib/irb/workspace.rb | 6 +- test/irb/test_command.rb | 143 ++++++++++++++++++++++++++++---- test/irb/test_completion.rb | 7 ++ test/irb/test_type_completor.rb | 7 +- 36 files changed, 414 insertions(+), 328 deletions(-) create mode 100644 lib/irb/command/context.rb diff --git a/lib/irb.rb b/lib/irb.rb index 0855c59de005d1..edc4fc5d535014 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -929,7 +929,7 @@ class Irb # Creates a new irb session def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) - @context.workspace.load_commands_to_main + @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @scanner = RubyLex.new @line_no = 1 @@ -950,7 +950,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.replace_workspace(workspace) - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: @@ -1028,7 +1028,15 @@ def eval_input return statement.code end - @context.evaluate(statement.evaluable_code, line_no) + case statement + when Statement::EmptyInput + # Do nothing + when Statement::Expression + @context.evaluate(statement.code, line_no) + when Statement::Command + ret = statement.command_class.execute(@context, statement.arg) + @context.set_last_value(ret) + end if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1084,10 +1092,7 @@ def readmultiline end code << line - - # Accept any single-line input for symbol aliases or commands that transform - # args - return code if single_line_command?(code) + return code if command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) return code if terminated @@ -1114,23 +1119,36 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - command_or_alias, arg = code.split(/\s/, 2) - # Transform a non-identifier alias (@, $) or keywords (next, break) - command_name = @context.command_aliases[command_or_alias.to_sym] - command = command_name || command_or_alias - command_class = ExtendCommandBundle.load_command(command) - - if command_class - Statement::Command.new(code, command, arg, command_class) + if (command, arg = parse_command(code)) + command_class = ExtendCommandBundle.load_command(command) + Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) Statement::Expression.new(code, is_assignment_expression) end end - def single_line_command?(code) - command = code.split(/\s/, 2).first - @context.symbol_alias?(command) || @context.transform_args?(command) + def parse_command(code) + command_name, arg = code.strip.split(/\s+/, 2) + return unless code.lines.size == 1 && command_name + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = @context.command_aliases[command]) + return [alias_name, arg] + end + + # Check visibility + public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false + if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) + [command, arg] + end + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1148,9 +1166,7 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform - # args - next true if single_line_command?(code) + next true if command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) terminated diff --git a/lib/irb/command.rb b/lib/irb/command.rb index ef930492370c7a..43cbda36b515d8 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -12,29 +12,17 @@ module Command; end # Installs the default irb extensions command bundle. module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: - - # See #install_alias_method. + # See ExtendCommandBundle.execute_as_command?. NO_OVERRIDE = 0 - # See #install_alias_method. OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. OVERRIDE_ALL = 0x02 - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - def irb_context - IRB.CurrentContext - end - - @ALIASES = [ - [:context, :irb_context, NO_OVERRIDE], - [:conf, :irb_context, NO_OVERRIDE], - ] - - @EXTEND_COMMANDS = [ + [ + :irb_context, :Context, "command/context", + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], [ :irb_exit, :Exit, "command/exit", [:exit, OVERRIDE_PRIVATE_ONLY], @@ -204,6 +192,26 @@ def irb_context ], ] + def self.command_override_policies + @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end @@commands = [] @@ -247,77 +255,13 @@ def self.load_command(command) nil end - # Installs the default irb commands. - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +cmd_name+ on the given +cmd_class+ Class. - # - # Will also define any given +aliases+ for the method. - # - # The optional +load_file+ parameter will be required within the method - # definition. def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - case cmd_class - when Symbol - cmd_class = cmd_class.id2name - when String - when Class - cmd_class = cmd_class.name - end - - line = __LINE__; eval %[ - def #{cmd_name}(*opts, **kwargs, &b) - Kernel.require_relative "#{load_file}" - ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line - - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end - end - - # Installs alias methods for the default irb commands, see - # ::install_extend_commands. - def install_alias_method(to, from, override = NO_OVERRIDE) - to = to.id2name unless to.kind_of?(String) - from = from.id2name unless from.kind_of?(String) + @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } + @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - if override == OVERRIDE_ALL or - (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or - (override == NO_OVERRIDE) && !respond_to?(to, true) - target = self - (class << self; self; end).instance_eval{ - if target.respond_to?(to, true) && - !target.respond_to?(EXCB.irb_original_method_name(to), true) - alias_method(EXCB.irb_original_method_name(to), to) - end - alias_method to, from - } - else - Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n" - end - end - - def self.irb_original_method_name(method_name) # :nodoc: - "irb_" + method_name + "_org" - end - - # Installs alias methods for the default irb commands on the given object - # using #install_alias_method. - def self.extend_object(obj) - unless (class << obj; ancestors; end).include?(EXCB) - super - for ali, com, flg in @ALIASES - obj.install_alias_method(ali, com, flg) - end - end + # Just clear memoized values + @@commands = [] + @@command_override_policies = nil end - - install_extend_commands end end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 47e5e60724029d..610f9ee22c70f1 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -7,12 +7,8 @@ module IRB module Command class Backtrace < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["backtrace", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}".rstrip) end end end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 3ce4f4d6c19922..ff74b5fb35438b 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,6 +10,10 @@ module IRB module Command class CommandArgumentError < StandardError; end + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + class Base class << self def category(category = nil) @@ -29,19 +33,13 @@ def help_message(help_message = nil) private - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end - def highlight(text) Color.colorize(text, [:BOLD, :BLUE]) end end - def self.execute(irb_context, *opts, **kwargs, &block) - command = new(irb_context) - command.execute(*opts, **kwargs, &block) + def self.execute(irb_context, arg) + new(irb_context).execute(arg) rescue CommandArgumentError => e puts e.message end @@ -52,7 +50,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + + def execute(arg) #nop end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index fa200f2d7811dc..42ee002ce8a2e5 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -7,12 +7,8 @@ module IRB module Command class Break < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(args = nil) - super(pre_cmds: "break #{args}") + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}".rstrip) end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 6b2edff5e5e6f9..655c77d8af7157 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -7,12 +7,8 @@ module IRB module Command class Catch < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["catch", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}".rstrip) end end end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e1047686c520cf..e0a406885f87c1 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -14,7 +14,7 @@ class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute(_arg) irb_context.main end end @@ -23,8 +23,13 @@ class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." - def execute(*obj) - irb_context.change_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end irb_context.main end end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 00000000000000..b4fc807343c0f5 --- /dev/null +++ b/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 8b6ffc860ce6c9..49e4384eb3eb78 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -7,8 +7,8 @@ module IRB module Command class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}".rstrip) end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index bdf91766bcc335..aeafe19b5fcc6f 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -13,7 +13,11 @@ class Debug < Base binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(pre_cmds: nil, do_cmds: nil) + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) if irb_context.with_debugger # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. if cmd = pre_cmds || do_cmds diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index a36b4577b0c23e..4b45a51e73cca3 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -7,8 +7,8 @@ module IRB module Command class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}".rstrip) end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index ab8c62663ea250..480100bfc7c2df 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -26,19 +26,9 @@ class Edit < Base edit Foo#bar HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.nil? || args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(*args) - path = args.first + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) if path.nil? path = @irb_context.irb_path diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index 05501819e2d0cd..c1d62357f4706d 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -7,8 +7,8 @@ module IRB module Command class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}".rstrip) end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbfed843..c9f16e05bc50b4 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,27 +6,16 @@ class Help < Base category "Help" description "List all available commands. Use `help ` to get information about a specific command." - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(command_name = nil) + def execute(command_name) content = - if command_name + if command_name.empty? + help_message + else if command_class = ExtendCommandBundle.load_command(command_name) command_class.help_message || command_class.description else "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" end - else - help_message end Pager.page_content(content) end diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index a47a8795ddc3f0..90f87f91023554 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -12,14 +12,12 @@ class History < Base category "IRB" description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - def self.transform_args(args) - match = args&.match(/(-g|-G)\s+(?.+)\s*\n\z/) - return unless match + def execute(arg) - "grep: #{Regexp.new(match[:grep]).inspect}" - end + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + end - def execute(grep: nil) formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| next if grep && !input.match?(grep) diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index a67be3eb858658..897ee2c4307956 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -7,12 +7,8 @@ module IRB module Command class Info < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["info", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}".rstrip) end end end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index cc93fdcbd5586c..6d868de94c8c76 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -8,7 +8,7 @@ class IrbInfo < Base category "IRB" description "Show information about IRB." - def execute + def execute(_arg) str = "Ruby version: #{RUBY_VERSION}\n" str += "IRB version: #{IRB.version}\n" str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 9e89a7b7f35751..33e327f4a93838 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -21,7 +21,12 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil, priv = nil) raise_cmd_argument_error unless file_name irb_load(file_name, priv) end @@ -30,7 +35,13 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") @@ -63,7 +74,12 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name source_file(file_name) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 6b6136c2feb78e..f6b19648640489 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -20,27 +20,35 @@ class Ls < Base -g [query] Filter the output with a query. HELP_MESSAGE - def self.transform_args(args) - if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) - args = match[:args] - "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:target].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:target]) + end + grep = Regexp.new(match[:grep]) else - args + args, kwargs = ruby_args(arg) + use_main = args.empty? + obj = args.first + grep = kwargs[:grep] + end + + if use_main + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables end - end - def execute(*arg, grep: nil) o = Output.new(grep: grep) - obj = arg.empty? ? irb_context.workspace.main : arg.first - locals = arg.empty? ? irb_context.workspace.binding.local_variables : [] klass = (obj.class == Class || obj.class == Module ? obj : obj.class) o.dump("constants", obj.constants) if obj.respond_to?(:constants) dump_methods(o, klass, obj) o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) - o.dump("locals", locals) + o.dump("locals", locals) if locals o.print_result end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index ee7927b010bdc0..70dc69cdec5ef5 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -10,15 +10,19 @@ def initialize(*args) super(*args) end - def execute(type = nil, arg = nil) - # Please check IRB.init_config in lib/irb/init.rb that sets - # IRB.conf[:MEASURE_PROC] to register default "measure" methods, - # "measure :time" (abbreviated as "measure") and "measure :stackprof". - - if block_given? + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' return end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(type = nil, arg = nil) + # Please check IRB.init_config in lib/irb/init.rb that sets + # IRB.conf[:MEASURE_PROC] to register default "measure" methods, + # "measure :time" (abbreviated as "measure") and "measure :stackprof". case type when :off diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 6487c9d24cb6ee..92d28e33ef19a1 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -7,8 +7,8 @@ module IRB module Command class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}".rstrip) end end end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index 888fe466f1a176..b51928c6508a92 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -14,7 +14,7 @@ class Workspaces < Base category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| truncated_inspect(ws.main) end @@ -39,8 +39,13 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) - irb_context.push_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end super end end @@ -49,8 +54,8 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) - irb_context.pop_workspace(*obj) + def execute(_arg) + irb_context.pop_workspace super end end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index 4dde28bee9bbaf..f9393cd3b5cab9 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,17 +3,6 @@ module IRB module Command class ShowDoc < Base - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - category "Context" description "Look up documentation with RI." @@ -31,7 +20,9 @@ def transform_args(args) HELP_MESSAGE - def execute(*names) + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -39,15 +30,13 @@ def execute(*names) ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) end - if names.empty? + if name.nil? Ri.interactive else - names.each do |name| - begin - Ri.display_name(name.to_s) - rescue RDoc::RI::Error - puts $!.message - end + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message end end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index 32bdf74d313e92..c4c8fc004121c2 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -24,18 +24,9 @@ class ShowSource < Base show_source Foo::BAR HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(str = nil) + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) unless str.is_a?(String) puts "Error: Expected a string but got #{str.inspect}" return diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index cce7d2b0f8443a..51498130293fa1 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -7,8 +7,8 @@ module IRB module Command class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}".rstrip) end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 5cc7b8c6f85fa2..24428a5c130ed7 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,10 +9,6 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -36,7 +32,12 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) print_deprecated_warning if irb_context.with_debugger @@ -44,7 +45,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +54,7 @@ class Jobs < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "List of current sessions." - def execute + def execute(_arg) print_deprecated_warning if irb_context.with_debugger @@ -61,7 +62,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +71,12 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) print_deprecated_warning if irb_context.with_debugger @@ -78,7 +84,7 @@ def execute(key = nil) return end - super + extend_irb_context raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key IRB.JobManager.switch(key) @@ -89,7 +95,12 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) print_deprecated_warning if irb_context.with_debugger @@ -97,7 +108,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb index d6658d70430e7f..c8439f12120333 100644 --- a/lib/irb/command/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -8,7 +8,7 @@ class Whereami < Base category "Context" description "Show the source code around binding.irb again." - def execute(*) + def execute(_arg) code = irb_context.workspace.code_around_binding if code puts code diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index b3813e89391d22..8a1df11561283b 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -86,6 +86,14 @@ def retrieve_files_to_require_from_load_path ) end + def command_completions(preposing, target) + if preposing.empty? && !target.empty? + IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + else + [] + end + end + def retrieve_files_to_require_relative_from_current_dir @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') @@ -103,9 +111,11 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) + commands = command_completions(preposing, target) result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - return [] unless result - result.completion_candidates.map { target + _1 } + return commands unless result + + commands | result.completion_candidates.map { target + _1 } end def doc_namespace(preposing, matched, _postposing, bind:) @@ -181,7 +191,8 @@ def completion_candidates(preposing, target, postposing, bind:) result = complete_require_path(target, preposing, postposing) return result if result end - retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands = command_completions(preposing || '', target) + commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } end def doc_namespace(_preposing, matched, _postposing, bind:) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 60dfb9668dac3c..e3c41924595a29 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -646,17 +646,5 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end - - # Return true if it's aliased from the argument and it's not an identifier. - def symbol_alias?(command) - return nil if command.match?(/\A\w+\z/) - command_aliases.key?(command.to_sym) - end - - # Return true if the command supports transforming args - def transform_args?(command) - command = command_aliases.fetch(command.to_sym, command) - ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) - end end end diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index 87fe03e23d953d..60e8afe31f88f2 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -29,11 +29,9 @@ def change_workspace(*_main) return main end - replace_workspace(WorkSpace.new(_main[0])) - - if !(class< @command_class end - - def evaluable_code - # Hook command-specific transformation to return valid Ruby code - if @command_class.respond_to?(:transform_args) - arg = @command_class.transform_args(@arg) - else - arg = @arg - end - - [@command, arg].compact.join(' ') - end end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425e1183..1490f7b478d432 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + if !(class< nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out) + assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out) end def test_measure_with_proc_warning @@ -402,7 +496,6 @@ def test_measure_with_proc_warning out, err = execute_lines( "3\n", "measure do\n", - "end\n", "3\n", conf: conf, main: c @@ -554,7 +647,8 @@ def test_popws_replaces_the_current_workspace_with_the_previous_one out, err = execute_lines( "pushws Foo.new\n", "popws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -573,7 +667,8 @@ class ChwsTest < WorkspaceCommandTestCase def test_chws_replaces_the_current_workspace out, err = execute_lines( "chws #{self.class}::Foo.new\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}::Foo") @@ -582,7 +677,8 @@ def test_chws_replaces_the_current_workspace def test_chws_does_nothing_when_receiving_no_argument out, err = execute_lines( "chws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -730,18 +826,18 @@ def test_ls_grep def test_ls_grep_empty out, err = execute_lines("ls\n") assert_empty err - assert_match(/whereami/, out) - assert_match(/show_source/, out) + assert_match(/assert/, out) + assert_match(/refute/, out) [ - "ls grep: /whereami/\n", - "ls -g whereami\n", - "ls -G whereami\n", + "ls grep: /assert/\n", + "ls -g assert\n", + "ls -G assert\n", ].each do |line| out, err = execute_lines(line) assert_empty err - assert_match(/whereami/, out) - assert_not_match(/show_source/, out) + assert_match(/assert/, out) + assert_not_match(/refute/, out) end end @@ -951,4 +1047,19 @@ def test_history_grep end + class HelperMethodInsallTest < CommandTestCase + def test_helper_method_install + IRB::ExtendCommandBundle.module_eval do + def foobar + "test_helper_method_foobar" + end + end + + out, err = execute_lines("foobar.upcase") + assert_empty err + assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"') + ensure + IRB::ExtendCommandBundle.remove_method :foobar + end + end end diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 8194463931ef70..5fe7952b3d70e6 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -14,6 +14,13 @@ def doc_namespace(target, bind) IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind) end + class CommandCompletionTest < CompletionTest + def test_command_completion + assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end + end + class MethodCompletionTest < CompletionTest def test_complete_string assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase") diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index cf4fc12c9f4fb0..5ed8988b34928e 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -9,7 +9,7 @@ return end -require 'irb/completion' +require 'irb' require 'tempfile' require_relative './helper' @@ -54,6 +54,11 @@ def test_empty_completion assert_equal [], candidates assert_doc_namespace('(', ')', nil) end + + def test_command_completion + assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end end class TypeCompletorIntegrationTest < IntegrationTestCase From d60b2caa95b01f37d35db9ef8be1d035d14b408d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Apr 2024 02:10:20 +0900 Subject: [PATCH 052/135] Lock turbo_tests to 2.1.0 provisionally turbo_tests 2.1.1 adds json to its dependency and the current bundler does not take the standard library json and fails to build as a gem before the installation. --- tool/bundler/dev_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/bundler/dev_gems.rb b/tool/bundler/dev_gems.rb index cb4b556d2fbc66..acf733557873f4 100644 --- a/tool/bundler/dev_gems.rb +++ b/tool/bundler/dev_gems.rb @@ -7,7 +7,7 @@ gem "rb_sys" gem "webrick", "~> 1.6" -gem "turbo_tests", "~> 2.1" +gem "turbo_tests", "= 2.1.0" gem "parallel_tests", "< 3.9.0" gem "parallel", "~> 1.19" gem "rspec-core", "~> 3.12" From d75dc3988059ac2fc6eb06f29508ef935f5e0139 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 11 Apr 2024 01:33:40 +0800 Subject: [PATCH 053/135] [ruby/irb] Centralize rstrip calls (https://github.com/ruby/irb/pull/918) https://github.com/ruby/irb/commit/97898b6251 --- lib/irb/command/backtrace.rb | 2 +- lib/irb/command/break.rb | 2 +- lib/irb/command/catch.rb | 2 +- lib/irb/command/continue.rb | 2 +- lib/irb/command/debug.rb | 3 +++ lib/irb/command/delete.rb | 2 +- lib/irb/command/finish.rb | 2 +- lib/irb/command/info.rb | 2 +- lib/irb/command/next.rb | 2 +- lib/irb/command/step.rb | 2 +- 10 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 610f9ee22c70f1..687bb075ace9fc 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -8,7 +8,7 @@ module IRB module Command class Backtrace < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "backtrace #{arg}".rstrip) + execute_debug_command(pre_cmds: "backtrace #{arg}") end end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index 42ee002ce8a2e5..a8f81fe665077b 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -8,7 +8,7 @@ module IRB module Command class Break < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "break #{arg}".rstrip) + execute_debug_command(pre_cmds: "break #{arg}") end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 655c77d8af7157..529dcbca5aed09 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -8,7 +8,7 @@ module IRB module Command class Catch < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "catch #{arg}".rstrip) + execute_debug_command(pre_cmds: "catch #{arg}") end end end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 49e4384eb3eb78..0daa029b154042 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -8,7 +8,7 @@ module IRB module Command class Continue < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "continue #{arg}".rstrip) + execute_debug_command(do_cmds: "continue #{arg}") end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index aeafe19b5fcc6f..f9aca0a672b4c6 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -18,6 +18,9 @@ def execute(_arg) end def execute_debug_command(pre_cmds: nil, do_cmds: nil) + pre_cmds = pre_cmds&.rstrip + do_cmds = do_cmds&.rstrip + if irb_context.with_debugger # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. if cmd = pre_cmds || do_cmds diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index 4b45a51e73cca3..2a57a4a3de06d2 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -8,7 +8,7 @@ module IRB module Command class Delete < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "delete #{arg}".rstrip) + execute_debug_command(pre_cmds: "delete #{arg}") end end end diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index c1d62357f4706d..3311a0e6e9e405 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -8,7 +8,7 @@ module IRB module Command class Finish < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "finish #{arg}".rstrip) + execute_debug_command(do_cmds: "finish #{arg}") end end end diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index 897ee2c4307956..d08ce00a320aab 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -8,7 +8,7 @@ module IRB module Command class Info < DebugCommand def execute(arg) - execute_debug_command(pre_cmds: "info #{arg}".rstrip) + execute_debug_command(pre_cmds: "info #{arg}") end end end diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 92d28e33ef19a1..3fc6b68d21f4b3 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -8,7 +8,7 @@ module IRB module Command class Next < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "next #{arg}".rstrip) + execute_debug_command(do_cmds: "next #{arg}") end end end diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index 51498130293fa1..29e5e35ac0135b 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -8,7 +8,7 @@ module IRB module Command class Step < DebugCommand def execute(arg) - execute_debug_command(do_cmds: "step #{arg}".rstrip) + execute_debug_command(do_cmds: "step #{arg}") end end end From 77d3996897ce57dba6d8dda9fd3503e7d0fe3c2f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 10 Apr 2024 15:45:04 -0400 Subject: [PATCH 054/135] [ruby/prism] Put in an unreachable assert for rescues parsing https://github.com/ruby/prism/commit/7a60b61368 --- prism/prism.c | 1 + 1 file changed, 1 insertion(+) diff --git a/prism/prism.c b/prism/prism.c index 16dad45b37b7e9..3bc1a9ce2103f7 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13859,6 +13859,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_RESCUE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_RESCUE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_RESCUE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } pm_statements_node_t *statements = parse_statements(parser, context); From f389a211b54aa86ec6f2a9edc641836eced96332 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Apr 2024 16:32:49 -0400 Subject: [PATCH 055/135] Fix indentation in switch statement in gc.c --- gc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gc.c b/gc.c index e5265616688551..3e00c0af43af15 100644 --- a/gc.c +++ b/gc.c @@ -9363,15 +9363,15 @@ gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data) for (; v != (VALUE)vend; v += stride) { asan_unpoisoning_object(v) { switch (BUILTIN_TYPE(v)) { - case T_NONE: - case T_ZOMBIE: + case T_NONE: + case T_ZOMBIE: break; - case T_STRING: + case T_STRING: // precompute the string coderange. This both save time for when it will be // eventually needed, and avoid mutating heap pages after a potential fork. rb_enc_str_coderange(v); // fall through - default: + default: if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) { RVALUE_AGE_SET_CANDIDATE(objspace, v); } From 38e3819be63a0d4be094e7cd478cf8532a7d1d9d Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 11 Apr 2024 07:16:27 +0800 Subject: [PATCH 056/135] [ruby/irb] Add a workaround to make IRB work with debug's tests (https://github.com/ruby/irb/pull/919) https://github.com/ruby/irb/commit/eb442c4dda --- lib/irb.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/irb.rb b/lib/irb.rb index edc4fc5d535014..99fd1c5df01bb8 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1035,6 +1035,12 @@ def eval_input @context.evaluate(statement.code, line_no) when Statement::Command ret = statement.command_class.execute(@context, statement.arg) + # TODO: Remove this output once we have a better way to handle it + # This is to notify `debug`'s test framework that the current input has been processed + # We also need to have a way to restart/stop threads around command execution + # when being used as `debug`'s console. + # https://github.com/ruby/debug/blob/master/lib/debug/irb_integration.rb#L8-L13 + puts "INTERNAL_INFO: {}" if @context.with_debugger && ENV['RUBY_DEBUG_TEST_UI'] == 'terminal' @context.set_last_value(ret) end From 39be11a17a221387b7eedd455114d6e87088ac60 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 10 Apr 2024 10:28:51 +0900 Subject: [PATCH 057/135] Fix segv when parsing `command` by ripper 89cfc152071 made this event dispatch to pass `Qundef` to user defined callback method by mistake. This commit fix it to be `nil`. --- parse.y | 2 +- test/ripper/test_sexp.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/parse.y b/parse.y index befc84c6d496ac..0d392369c472d1 100644 --- a/parse.y +++ b/parse.y @@ -3447,7 +3447,7 @@ command : fcall command_args %prec tLOWEST { set_embraced_location($5, &@4, &@6); $$ = new_command_qcall(p, idCOLON2, $1, $3, Qnull, $5, &@3, &@$); - /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qundef), $:5) %*/ + /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qnil), $:5) %*/ } | keyword_super command_args { diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index 32285be9abac0e..3ebcf3062ee243 100644 --- a/test/ripper/test_sexp.rb +++ b/test/ripper/test_sexp.rb @@ -123,6 +123,21 @@ def test_named_with_default assert_equal(exp, named) end + def test_command + sexp = Ripper.sexp("a::C {}") + assert_equal( + [:program, + [ + [:method_add_block, + [:command_call, + [:vcall, [:@ident, "a", [1, 0]]], + [:@op, "::", [1, 1]], + [:@const, "C", [1, 3]], + nil], + [:brace_block, nil, [[:void_stmt]]]]]], + sexp) + end + def search_sexp(sym, sexp) return sexp if !sexp or sexp[0] == sym sexp.find do |e| From 76732b3e7b42d23290cd96cd695b2373172c8a43 Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Wed, 10 Apr 2024 21:05:59 +0900 Subject: [PATCH 058/135] Remove unused AREF macro --- parse.y | 2 -- 1 file changed, 2 deletions(-) diff --git a/parse.y b/parse.y index 0d392369c472d1..f31a3ed55ec652 100644 --- a/parse.y +++ b/parse.y @@ -332,8 +332,6 @@ RBIMPL_WARNING_POP() #define NO_LEX_CTXT (struct lex_context){0} -#define AREF(ary, i) RARRAY_AREF(ary, i) - #ifndef WARN_PAST_SCOPE # define WARN_PAST_SCOPE 0 #endif From 87ad5ca3ca32624cb4817504d258c9a348fd279e Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 10 Apr 2024 18:30:20 +0900 Subject: [PATCH 059/135] Remove unused function from `struct rb_parser_config_struct` --- ruby_parser.c | 1 - rubyparser.h | 1 - universal_parser.c | 2 -- 3 files changed, 4 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index 2d914e865b398a..12bfb18b1a5e9e 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -567,7 +567,6 @@ static const rb_parser_config_t rb_global_parser_config = { .qnil = Qnil, .qtrue = Qtrue, .qfalse = Qfalse, - .qundef = Qundef, .eArgError = arg_error, .long2int = rb_long2int, diff --git a/rubyparser.h b/rubyparser.h index 268e8deeff6f41..5ba2da8de11f98 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1408,7 +1408,6 @@ typedef struct rb_parser_config_struct { VALUE qnil; VALUE qtrue; VALUE qfalse; - VALUE qundef; VALUE (*eArgError)(void); int (*long2int)(long); diff --git a/universal_parser.c b/universal_parser.c index ecbc242f08ebb1..e6822c1bdc3545 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -245,8 +245,6 @@ #define Qtrue p->config->qtrue #undef Qfalse #define Qfalse p->config->qfalse -#undef Qundef -#define Qundef p->config->qundef #define rb_eArgError p->config->eArgError() #undef rb_long2int #define rb_long2int p->config->long2int From 5d9fd674c9ceefd6baffdd05d33c5b60a7c64e42 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 10 Apr 2024 14:57:14 +0900 Subject: [PATCH 060/135] put empty `rb_gc_force_recycle()` and declare it will be removed soon. ddtrace is still referes the API and build was failed. See https://github.com/DataDog/dd-trace-rb/pull/3578 Maybe threre are only few users of this C-API now so we can remove it soon. --- include/ruby/internal/gc.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index f36ec1590e4524..462f416af21cfd 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -823,4 +823,7 @@ rb_obj_write( return a; } +RBIMPL_ATTR_DEPRECATED(("Will be removed soon")) +static inline void rb_gc_force_recycle(VALUE obj){} + #endif /* RBIMPL_GC_H */ From 207788466eedfdefcf09fdc5c6217547b5ce4ed1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 10 Apr 2024 22:44:33 +0900 Subject: [PATCH 061/135] [Bug #20417] Block local variables do not need to warn about unused --- parse.y | 4 ++++ test/ruby/test_ast.rb | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/parse.y b/parse.y index f31a3ed55ec652..8e1a6e4a46d071 100644 --- a/parse.y +++ b/parse.y @@ -13814,6 +13814,10 @@ new_bv(struct parser_params *p, ID name) } if (!shadowing_lvar_0(p, name)) return; dyna_var(p, name); + ID *vidp = 0; + if (dvar_defined_ref(p, name, &vidp)) { + if (vidp) *vidp |= LVAR_USED; + } } static void diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 90d19c3d68eb74..29da607fc5daa1 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1232,6 +1232,12 @@ def test_with_bom EXP end + def test_unused_block_local_variable + assert_warning('') do + RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}}) + end + end + def assert_error_tolerant(src, expected, keep_tokens: false) begin verbose_bak, $VERBOSE = $VERBOSE, false From e9fd34750f96a9fd1aed94b436996b25685d48d9 Mon Sep 17 00:00:00 2001 From: Vivek Gupta <139687128+vwake01@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:05:02 +0530 Subject: [PATCH 062/135] [DOC] Typo fix in NEWS.md Fix https://github.com/ruby/ruby/pull/10366 --- NEWS.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 18c030909c5d56..bdb4fa1eb1397b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,17 +8,16 @@ Note that each entry is kept to a minimum, see links for details. ## Language changes * String literals in files without a `frozen_string_literal` comment now behave - as if they were frozen. If they are mutated a deprecation warning is emited. + as if they were frozen. If they are mutated a deprecation warning is emitted. These warnings can be enabled with `-W:deprecated` or by setting `Warning[:deprecated] = true`. - To disable this change you can run Ruby with the `--disable-frozen-string-literal` command line - argument. [[Feature #20205]] + To disable this change, you can run Ruby with the `--disable-frozen-string-literal` + command line argument. [[Feature #20205]] * `it` is added to reference a block parameter. [[Feature #18980]] * Keyword splatting `nil` when calling methods is now supported. - `**nil` is treated similar to `**{}`, passing no keywords, - and not calling any conversion methods. - [[Bug #20064]] + `**nil` is treated similarly to `**{}`, passing no keywords, + and not calling any conversion methods. [[Bug #20064]] * Block passing is no longer allowed in index. [[Bug #19918]] From 501a32c6300e5ae7fe2bef16c2552b96084b32e9 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Thu, 11 Apr 2024 12:02:11 +0900 Subject: [PATCH 063/135] Remove duplicated `st_init_table_with_size` definition `st_init_table_with_size` is already defined in universal_parser.c. --- universal_parser.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/universal_parser.c b/universal_parser.c index e6822c1bdc3545..d555ea00e88779 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -252,8 +252,5 @@ #define rb_enc_isascii p->config->enc_isascii #define rb_enc_mbc_to_codepoint p->config->enc_mbc_to_codepoint -#undef st_init_table_with_size -#define st_init_table_with_size rb_parser_st_init_table_with_size - #define rb_ast_new() \ rb_ast_new(p->config) From ed303cd56cfc7889ce371ee390e9fd36f86814ea Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 10 Apr 2024 23:53:53 -0700 Subject: [PATCH 064/135] Fix a typo in a comment --- yjit/src/invariants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index c4facb31dd00d9..e4602934409dac 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -416,7 +416,7 @@ pub fn block_assumptions_free(blockref: BlockRef) { invariants.constant_state_blocks.shrink_to_fit(); } - // Remove tracking for blocks assumping no singleton class + // Remove tracking for blocks assuming no singleton class for (_, blocks) in invariants.no_singleton_classes.iter_mut() { blocks.remove(&blockref); } From 1b830740ba8371c4bcfdfc6eb2cb7e0ae81a84e0 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 10 Apr 2024 10:50:18 +0200 Subject: [PATCH 065/135] compile.c: use rb_enc_interned_str to reduce allocations The `rb_fstring(rb_enc_str_new())` pattern is inneficient because: - It passes a mutable string to `rb_fstring` so if it has to be interned it will first be duped. - It an equivalent interned string already exists, we allocated the string for nothing. With `rb_enc_interned_str` we either directly get the pre-existing string with 0 allocations, or efficiently directly intern the one we create without first duping it. --- compile.c | 32 +++++++++++++------------------- internal/ruby_parser.h | 1 + parse.y | 2 +- ruby_parser.c | 8 ++++++++ test/ruby/test_shapes.rb | 2 +- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/compile.c b/compile.c index d509957dac2432..3fb1566ad47d90 100644 --- a/compile.c +++ b/compile.c @@ -4338,7 +4338,7 @@ compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *cons while (list) { const NODE *const head = list->nd_head; if (nd_type_p(head, NODE_STR)) { - lit = rb_fstring(rb_node_str_string_val(head)); + lit = rb_node_str_string_val(head); ADD_INSN1(ret, head, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); lit = Qnil; @@ -4377,7 +4377,7 @@ compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) { int cnt; if (!RNODE_DSTR(node)->nd_next) { - VALUE lit = rb_fstring(rb_node_dstr_string_val(node)); + VALUE lit = rb_node_dstr_string_val(node); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -4765,14 +4765,13 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) case NODE_FILE: case NODE_STR: if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { - VALUE lit; VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX((int)nd_line(node))); - lit = rb_str_dup(get_string_value(node)); + VALUE lit = rb_str_dup(get_string_value(node)); rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); return rb_str_freeze(lit); } else { - return rb_fstring(get_string_value(node)); + return get_string_value(node); } default: rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); @@ -5142,9 +5141,9 @@ rb_node_case_when_optimizable_literal(const NODE *const node) case NODE_LINE: return rb_node_line_lineno_val(node); case NODE_STR: - return rb_fstring(rb_node_str_string_val(node)); + return rb_node_str_string_val(node); case NODE_FILE: - return rb_fstring(rb_node_file_path_val(node)); + return rb_node_file_path_val(node); } return Qundef; } @@ -5166,7 +5165,7 @@ when_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, if (nd_type_p(val, NODE_STR) || nd_type_p(val, NODE_FILE)) { debugp_param("nd_lit", get_string_value(val)); - lit = rb_fstring(get_string_value(val)); + lit = get_string_value(val); ADD_INSN1(cond_seq, val, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -8445,7 +8444,7 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE get_nd_args(node) == NULL && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(get_nd_recv(node))); + VALUE str = get_string_value(get_nd_recv(node)); if (get_node_call_nd_mid(node) == idUMinus) { ADD_INSN2(ret, line_node, opt_str_uminus, str, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); @@ -8469,7 +8468,7 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE ISEQ_COMPILE_DATA(iseq)->current_block == NULL && !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(get_nd_args(node))->nd_head)); + VALUE str = get_string_value(RNODE_LIST(get_nd_args(node))->nd_head); CHECK(COMPILE(ret, "recv", get_nd_recv(node))); ADD_INSN2(ret, line_node, opt_aref_with, str, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); @@ -9746,7 +9745,7 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head)); + VALUE str = get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head); CHECK(COMPILE(ret, "recv", RNODE_ATTRASGN(node)->nd_recv)); CHECK(COMPILE(ret, "value", RNODE_LIST(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_next)->nd_head)); if (!popped) { @@ -9971,7 +9970,7 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa return COMPILE_OK; case NODE_STR:{ - VALUE lit = rb_fstring(rb_node_str_string_val(node)); + VALUE lit = rb_node_str_string_val(node); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); *value_p = lit; @@ -9981,7 +9980,7 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa } case NODE_FILE:{ - VALUE lit = rb_fstring(rb_node_file_path_val(node)); + VALUE lit = rb_node_file_path_val(node); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); *value_p = lit; @@ -10576,12 +10575,10 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no VALUE lit = get_string_value(node); switch (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { case ISEQ_FROZEN_STRING_LITERAL_UNSET: - lit = rb_fstring(lit); ADD_INSN1(ret, node, putchilledstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; case ISEQ_FROZEN_STRING_LITERAL_DISABLED: - lit = rb_fstring(lit); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; @@ -10592,9 +10589,6 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); lit = rb_str_freeze(lit); } - else { - lit = rb_fstring(lit); - } ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; @@ -10614,7 +10608,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_XSTR:{ ADD_CALL_RECEIVER(ret, node); - VALUE str = rb_fstring(rb_node_str_string_val(node)); + VALUE str = rb_node_str_string_val(node); ADD_INSN1(ret, node, putobject, str); RB_OBJ_WRITTEN(iseq, Qundef, str); ADD_CALL(ret, node, idBackquote, INT2FIX(1)); diff --git a/internal/ruby_parser.h b/internal/ruby_parser.h index 7dc6a46d4f42ac..0a00075211fba8 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -19,6 +19,7 @@ VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); VALUE rb_parser_new(void); rb_ast_t *rb_parser_compile_string_path(VALUE vparser, VALUE fname, VALUE src, int line); VALUE rb_str_new_parser_string(rb_parser_string_t *str); +VALUE rb_str_new_mutable_parser_string(rb_parser_string_t *str); VALUE rb_node_str_string_val(const NODE *); VALUE rb_node_sym_string_val(const NODE *); diff --git a/parse.y b/parse.y index 8e1a6e4a46d071..58e39e8f2db94d 100644 --- a/parse.y +++ b/parse.y @@ -7959,7 +7959,7 @@ nextline(struct parser_params *p, int set_encoding) } #ifndef RIPPER if (p->debug_lines) { - VALUE v = rb_str_new_parser_string(str); + VALUE v = rb_str_new_mutable_parser_string(str); if (set_encoding) rb_enc_associate(v, p->enc); rb_ary_push(p->debug_lines, v); } diff --git a/ruby_parser.c b/ruby_parser.c index 12bfb18b1a5e9e..40c469f14bb44b 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -741,6 +741,14 @@ rb_parser_set_yydebug(VALUE vparser, VALUE flag) VALUE rb_str_new_parser_string(rb_parser_string_t *str) +{ + VALUE string = rb_enc_interned_str(str->ptr, str->len, str->enc); + rb_enc_str_coderange(string); + return string; +} + +VALUE +rb_str_new_mutable_parser_string(rb_parser_string_t *str) { return rb_enc_str_new(str->ptr, str->len, str->enc); } diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index bbea03344e926d..9b02504384491c 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1006,7 +1006,7 @@ def test_freezing_and_cloning_object_with_ivars end def test_freezing_and_cloning_string - str = "str".freeze + str = ("str" + "str").freeze str2 = str.clone(freeze: true) assert_predicate(str2, :frozen?) assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) From e7f8db9079bc3b0f94a490e7597560ac07f5b072 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Apr 2024 12:36:06 +0900 Subject: [PATCH 066/135] [pty] Support `ptsname_r` of glibc Although glibc `ptsname_r` man page mentions Tru64 and HP-UX, this function appears to be declared obsolete on both. --- ext/pty/extconf.rb | 1 + ext/pty/pty.c | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index ba0c4286fd31d1..8cf9e30e69786a 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -16,6 +16,7 @@ if have_func("posix_openpt") or (util or have_func("openpty")) or have_func("_getpty") or + have_func("ptsname_r") or have_func("ptsname") or have_func("ioctl") create_makefile('pty') diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 7667485579ec56..5e66abcbe217d4 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -256,7 +256,21 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, RB_GC_GUARD(carg.execarg_obj); } -#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME) +#if defined(HAVE_PTSNAME) && !defined(HAVE_PTSNAME_R) +/* glibc only, not obsolete interface on Tru64 or HP-UX */ +static int +ptsname_r(int fd, char *buf, size_t buflen) +{ + extern char *ptsname(int); + char *name = ptsname(fd); + if (!name) return -1; + strlcpy(buf, name, buflen); + return 0; +} +# define HAVE_PTSNAME_R 1 +#endif + +#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) static int no_mesg(char *slavedevice, int nomesg) { @@ -320,7 +334,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; - if ((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -333,7 +348,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; grantpt_error: @@ -387,7 +401,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, char *slavedevice; void (*s)(); - extern char *ptsname(int); extern int unlockpt(int); extern int grantpt(int); @@ -405,7 +418,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; - if((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -416,7 +430,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; error: From b2f8de3d9d7af9af547c1e290c6e3a4d2658a62e Mon Sep 17 00:00:00 2001 From: Naoto Ono Date: Mon, 8 Apr 2024 13:12:42 +0900 Subject: [PATCH 067/135] Launchable: Correctly configure the missing "os" flavor in ubuntu.yaml --- .github/workflows/ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b4bcb8223a08fc..419684e7f312ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -103,7 +103,7 @@ jobs: - name: Set up Launchable uses: ./.github/actions/launchable/setup with: - os: ${{ matrix.os }} + os: ${{ matrix.os || 'ubuntu-22.04' }} test-opts: ${{ matrix.configure }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build From 9183101aa7399a56a93045cc85220707bb413218 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Apr 2024 12:24:05 +0200 Subject: [PATCH 068/135] prism_compile.c: X_STRING should be frozen The backtick method recieves a frozen string unless it is interpolated. Otherwise the string held in the ISeq could be mutated by a custom backtick method. --- prism_compile.c | 2 +- spec/ruby/language/execution_spec.rb | 78 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index a26962cb942427..3a2f271dd6c901 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -8352,7 +8352,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // `foo` // ^^^^^ const pm_x_string_node_t *cast = (const pm_x_string_node_t *) node; - VALUE value = parse_string_encoded(scope_node, node, &cast->unescaped); + VALUE value = rb_fstring(parse_string_encoded(scope_node, node, &cast->unescaped)); PUSH_INSN(ret, location, putself); PUSH_INSN1(ret, location, putobject, value); diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb index 4e0310946dc3c5..ef1de38899809d 100644 --- a/spec/ruby/language/execution_spec.rb +++ b/spec/ruby/language/execution_spec.rb @@ -5,6 +5,45 @@ ip = 'world' `echo disc #{ip}`.should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + `test command` + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + `test #{:command}` + end + end + + called.should == true + end end describe "%x" do @@ -12,4 +51,43 @@ ip = 'world' %x(echo disc #{ip}).should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + %x{test command} + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + %x{test #{:command}} + end + end + + called.should == true + end end From 56f9ac8d31766426471aca3c8180defeba0c2fd0 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 08:12:23 -0400 Subject: [PATCH 069/135] [ruby/prism] More unreachables https://github.com/ruby/prism/commit/735f3122c2 --- prism/prism.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index 3bc1a9ce2103f7..a0ab6d7cee41b4 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13907,6 +13907,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ELSE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ELSE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ELSE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } else_statements = parse_statements(parser, context); @@ -13936,6 +13937,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ENSURE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ENSURE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ENSURE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } ensure_statements = parse_statements(parser, context); From db0cf1aef92efc558d4d6c6351dee9e45ef31005 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Apr 2024 12:47:49 +0200 Subject: [PATCH 070/135] prism_compile.c: use rb_enc_interned_str to reduce allocations The `rb_fstring(rb_enc_str_new())` pattern is inefficient because: - It passes a mutable string to `rb_fstring` so if it has to be interned it will first be duped. - It an equivalent interned string already exists, we allocated the string for nothing. With `rb_enc_interned_str` we either directly get the pre-existing string with 0 allocations, or efficiently directly intern the one we create without first duping it. --- prism_compile.c | 56 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 3a2f271dd6c901..2a13e1812dfec4 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -289,6 +289,35 @@ parse_string_encoded(const pm_scope_node_t *scope_node, const pm_node_t *node, c return rb_enc_str_new((const char *) pm_string_source(string), pm_string_length(string), encoding); } +static inline VALUE +parse_static_literal_string(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, const pm_string_t *string) +{ + rb_encoding *encoding; + + if (node->flags & PM_ENCODING_FLAGS_FORCED_BINARY_ENCODING) { + encoding = rb_ascii8bit_encoding(); + } + else if (node->flags & PM_ENCODING_FLAGS_FORCED_UTF8_ENCODING) { + encoding = rb_utf8_encoding(); + } + else { + encoding = scope_node->encoding; + } + + VALUE value = rb_enc_interned_str((const char *) pm_string_source(string), pm_string_length(string), encoding); + rb_enc_str_coderange(value); + + if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { + int line_number = pm_node_line_number(scope_node->parser, node); + VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line_number)); + value = rb_str_dup(value); + rb_ivar_set(value, id_debug_created_info, rb_obj_freeze(debug_info)); + rb_str_freeze(value); + } + + return value; +} + static inline ID parse_string_symbol(const pm_scope_node_t *scope_node, const pm_symbol_node_t *symbol) { @@ -572,7 +601,7 @@ pm_source_file_value(const pm_source_file_node_t *node, const pm_scope_node_t *s if (length > 0) { rb_encoding *filepath_encoding = scope_node->filepath_encoding != NULL ? scope_node->filepath_encoding : rb_utf8_encoding(); - return rb_fstring(rb_enc_str_new((const char *) pm_string_source(filepath), length, filepath_encoding)); + return rb_enc_interned_str((const char *) pm_string_source(filepath), length, filepath_encoding); } else { return rb_fstring_lit(""); @@ -688,9 +717,8 @@ pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, const pm_scope_n case PM_SOURCE_LINE_NODE: return INT2FIX(pm_node_line_number(scope_node->parser, node)); case PM_STRING_NODE: { - VALUE string = parse_string_encoded(scope_node, node, &((const pm_string_node_t *) node)->unescaped); - int line_number = pm_node_line_number(scope_node->parser, node); - return pm_static_literal_string(iseq, string, line_number); + const pm_string_node_t *cast = (const pm_string_node_t *) node; + return parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); } case PM_SYMBOL_NODE: return ID2SYM(parse_string_symbol(scope_node, (const pm_symbol_node_t *) node)); @@ -4290,10 +4318,7 @@ pm_compile_case_node_dispatch(rb_iseq_t *iseq, VALUE dispatch, const pm_node_t * break; case PM_STRING_NODE: { const pm_string_node_t *cast = (const pm_string_node_t *) node; - VALUE string = parse_string_encoded(scope_node, node, &cast->unescaped); - - int line_number = pm_node_line_number(scope_node->parser, node); - key = pm_static_literal_string(iseq, string, line_number); + key = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); break; } default: @@ -4684,7 +4709,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, switch (method_id) { case idUMinus: { if (pm_opt_str_freeze_p(iseq, cast)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); PUSH_INSN2(ret, location, opt_str_uminus, value, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); return; } @@ -4692,7 +4717,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } case idFreeze: { if (pm_opt_str_freeze_p(iseq, cast)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); PUSH_INSN2(ret, location, opt_str_freeze, value, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); return; } @@ -4701,7 +4726,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, case idAREF: { if (pm_opt_aref_with_p(iseq, cast)) { const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); PM_COMPILE_NOT_POPPED(cast->receiver); PUSH_INSN2(ret, location, opt_aref_with, value, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); @@ -4717,7 +4742,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, case idASET: { if (pm_opt_aset_with_p(iseq, cast)) { const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); PM_COMPILE_NOT_POPPED(cast->receiver); PM_COMPILE_NOT_POPPED(((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[1]); @@ -4942,7 +4967,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, else { if (PM_NODE_TYPE_P(condition, PM_STRING_NODE)) { const pm_string_node_t *string = (const pm_string_node_t *) condition; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, condition, &string->unescaped); PUSH_INSN1(cond_seq, location, putobject, value); } else { @@ -8226,8 +8251,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^ if (!popped) { const pm_string_node_t *cast = (const pm_string_node_t *) node; - VALUE value = parse_string_encoded(scope_node, node, &cast->unescaped); - value = pm_static_literal_string(iseq, value, location.line); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); if (PM_NODE_FLAG_P(node, PM_STRING_FLAGS_FROZEN)) { PUSH_INSN1(ret, location, putobject, value); @@ -8352,7 +8376,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // `foo` // ^^^^^ const pm_x_string_node_t *cast = (const pm_x_string_node_t *) node; - VALUE value = rb_fstring(parse_string_encoded(scope_node, node, &cast->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); PUSH_INSN(ret, location, putself); PUSH_INSN1(ret, location, putobject, value); From c2622b52536c5206a396de589de0961967df6956 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 11 Apr 2024 10:37:56 -0400 Subject: [PATCH 071/135] YJIT: x64: Remove register shuffle with `opt_and` and friends (#10498) This is best understood by looking at the change to the output: ```diff # Insn: 0002 opt_and (stack_size: 2) - mov rax, rsi - and rax, rdi - mov rsi, rax + and rsi, rdi ``` It's a bit awkward to match against due to how stack operands are lowered, but hey, it's nice to save the 2 unnecessary MOVs. --- yjit/src/backend/x86_64/mod.rs | 17 +++++++++++++++++ yjit/src/codegen.rs | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs index 19e604a2f8241e..d52ed265bd52e0 100644 --- a/yjit/src/backend/x86_64/mod.rs +++ b/yjit/src/backend/x86_64/mod.rs @@ -181,6 +181,23 @@ impl Assembler iterator.map_insn_index(&mut asm); iterator.next_unmapped(); // Pop merged Insn::Mov } + (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) + if out == src && live_ranges[index] == index + 1 && { + // We want to do `dest == left`, but `left` has already gone + // through lower_stack_opnd() while `dest` has not. So we + // lower `dest` before comparing. + let lowered_dest = if let Opnd::Stack { .. } = dest { + asm.lower_stack_opnd(dest) + } else { + *dest + }; + lowered_dest == *left + } => { + *out = *dest; + asm.push_insn(insn); + iterator.map_insn_index(&mut asm); + iterator.next_unmapped(); // Pop merged Insn::Mov + } _ => { match (unmapped_opnds[0], unmapped_opnds[1]) { (Opnd::Mem(_), Opnd::Mem(_)) => { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index be719023c01019..d60f4f0ec1029b 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -3827,7 +3827,7 @@ fn gen_opt_and( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { @@ -3867,7 +3867,7 @@ fn gen_opt_or( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { @@ -3909,7 +3909,7 @@ fn gen_opt_minus( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { From 2df4638538c5602bb14bfb5bf0cb68b06452e584 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Apr 2024 21:47:44 +0900 Subject: [PATCH 072/135] Cleanings of .bundle do not need cleanings of ext --- template/Makefile.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/Makefile.in b/template/Makefile.in index e0dcee1320ec67..46f55c1ff47fa4 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -518,9 +518,9 @@ ext/realclean:: ext/realclean.sub .bundle/distclean:: .bundle/distclean.sub .bundle/realclean:: .bundle/realclean.sub -ext/clean.sub .bundle/clean.sub:: ext/clean.mk -ext/distclean.sub .bundle/distclean.sub:: ext/distclean.mk -ext/realclean.sub .bundle/realclean.sub:: ext/realclean.mk +ext/clean.sub:: ext/clean.mk +ext/distclean.sub:: ext/distclean.mk +ext/realclean.sub:: ext/realclean.mk ext/clean.sub ext/distclean.sub ext/realclean.sub \ .bundle/clean.sub .bundle/distclean.sub .bundle/realclean.sub:: From ca81f5a5de4bf9a8445770edd13ab0a9321910bf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 11 Apr 2024 21:53:58 +0900 Subject: [PATCH 073/135] Realclean extracted bundled gems and lock files --- template/Makefile.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/template/Makefile.in b/template/Makefile.in index 46f55c1ff47fa4..d9a3cbc0650206 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -546,6 +546,9 @@ ext/distclean ext/realclean .bundle/distclean .bundle/realclean:: find "$$@" -type d -empty -exec $(RMDIRS) {} + 2> /dev/null || true $(Q) $(RMDIRS) $(@D) 2> /dev/null || true +.bundle/realclean:: + @$(RMALL) $(tooldir)/bunlder/*.lock $(srcdir)/.bundle + clean-enc distclean-enc realclean-enc: @test -f "$(ENC_MK)" || exit 0; \ echo $(@:-enc=ing) encodings; \ From f2369de2a4a0ffc5e40f6d55fa3ae811128432db Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Apr 2024 12:08:03 -0400 Subject: [PATCH 074/135] Remove redundant cast ptr is already of the VALUE type, so we don't need to cast it. --- gc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index 3e00c0af43af15..df3a3b8a775ab1 100644 --- a/gc.c +++ b/gc.c @@ -4477,8 +4477,8 @@ id2ref(VALUE objid) if (ptr == Qtrue) return Qtrue; if (ptr == Qfalse) return Qfalse; if (NIL_P(ptr)) return Qnil; - if (FIXNUM_P(ptr)) return (VALUE)ptr; - if (FLONUM_P(ptr)) return (VALUE)ptr; + if (FIXNUM_P(ptr)) return ptr; + if (FLONUM_P(ptr)) return ptr; ptr = obj_id_to_ref(objid); if ((ptr % sizeof(RVALUE)) == (4 << 2)) { From bb5ed8b3df9151d5bfadf13622c53888e140ea73 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 10:21:11 -0400 Subject: [PATCH 075/135] [PRISM] Fix flags on local variable operator write nodes --- prism_compile.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 2a13e1812dfec4..6fd9d35357ddb9 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6381,8 +6381,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_COMPILE_NOT_POPPED(cast->value); ID method_id = pm_constant_id_lookup(scope_node, cast->operator); - int flags = VM_CALL_ARGS_SIMPLE | VM_CALL_FCALL | VM_CALL_VCALL; - PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); if (!popped) PUSH_INSN(ret, location, dup); PUSH_SETLOCAL(ret, location, local_index.index, local_index.level); From 58f93eec188df0a77b61b4781a7baf7b2c608097 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 14:01:37 -0400 Subject: [PATCH 076/135] [PRISM] Fix break in super block --- prism_compile.c | 45 ++++++++++++++++++++++++++++++++++++++------- spec/prism.mspec | 1 - 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 6fd9d35357ddb9..fdba3dada05ec4 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -5791,6 +5791,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_forwarding_super_node_t *cast = (const pm_forwarding_super_node_t *) node; const rb_iseq_t *block = NULL; + const rb_iseq_t *previous_block; + LABEL *retry_label; + LABEL *retry_end_l; + + if (cast->block != NULL) { + previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + + retry_label = NEW_LABEL(location.line); + retry_end_l = NEW_LABEL(location.line); + + PUSH_LABEL(ret, retry_label); + } + PUSH_INSN(ret, location, putself); int flag = VM_CALL_ZSUPER | VM_CALL_SUPER | VM_CALL_FCALL; @@ -5798,7 +5812,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_scope_node_t next_scope_node; pm_scope_node_init((const pm_node_t *) cast->block, &next_scope_node, scope_node); - block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); + ISEQ_COMPILE_DATA(iseq)->current_block = block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); pm_scope_node_destroy(&next_scope_node); RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) block); } @@ -5898,8 +5912,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_SEQ(ret, args); PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flag, NULL, block != NULL), block); - if (popped) PUSH_INSN(ret, location, pop); + if (cast->block != NULL) { + PUSH_LABEL(ret, retry_end_l); + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, block, retry_end_l); + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + } + + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_GLOBAL_VARIABLE_AND_WRITE_NODE: { @@ -8271,8 +8291,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, DECL_ANCHOR(args); INIT_ANCHOR(args); - ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + LABEL *retry_label = NEW_LABEL(location.line); + LABEL *retry_end_l = NEW_LABEL(location.line); + + const rb_iseq_t *previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + const rb_iseq_t *current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NULL; + + PUSH_LABEL(ret, retry_label); PUSH_INSN(ret, location, putself); int flags = 0; @@ -8280,11 +8307,11 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, int argc = pm_setup_args(cast->arguments, cast->block, &flags, &keywords, iseq, ret, scope_node, &location); flags |= VM_CALL_SUPER | VM_CALL_FCALL; - const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; if (cast->block && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)) { pm_scope_node_t next_scope_node; pm_scope_node_init(cast->block, &next_scope_node, scope_node); - parent_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); pm_scope_node_destroy(&next_scope_node); } @@ -8293,9 +8320,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } PUSH_SEQ(ret, args); - PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, parent_block != NULL), parent_block); - + PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, current_block != NULL), current_block); + PUSH_LABEL(ret, retry_end_l); if (popped) PUSH_INSN(ret, location, pop); + + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, current_block, retry_end_l); + return; } case PM_SYMBOL_NODE: { diff --git a/spec/prism.mspec b/spec/prism.mspec index 9f5e2b2a5fdaa5..7279c1e9b1b06c 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -1,7 +1,6 @@ # frozen_string_literal: true ## Language -MSpec.register(:exclude, "Executing break from within a block works when passing through a super call") MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding an additional duplicate key afterwards") From cd516ebd20a8d2c7b0f912e4d5750f84616463a5 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 14:37:11 -0400 Subject: [PATCH 077/135] [ruby/prism] Add Location#chop https://github.com/ruby/prism/commit/5dd57f4b84 --- lib/prism/desugar_compiler.rb | 6 ++++-- lib/prism/parse_result.rb | 5 +++++ test/prism/ruby_api_test.rb | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index 8d059b0c989bcf..9b62c00df32213 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -73,6 +73,8 @@ def initialize(node, source, read_class, write_class, *arguments) # Desugar `x += y` to `x = x + y` def compile + operator_loc = node.operator_loc.chop + write_class.new( source, *arguments, @@ -82,8 +84,8 @@ def compile 0, read_class.new(source, *arguments, node.name_loc), nil, - node.operator_loc.slice.chomp("=").to_sym, - node.operator_loc.copy(length: node.operator_loc.length - 1), + operator_loc.slice.to_sym, + operator_loc, nil, ArgumentsNode.new(source, 0, [node.value], node.value.location), nil, diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index b6109b0993dde7..39e15f6027afc5 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -161,6 +161,11 @@ def copy(source: self.source, start_offset: self.start_offset, length: self.leng Location.new(source, start_offset, length) end + # Returns a new location that is the result of chopping off the last byte. + def chop + copy(length: length == 0 ? length : length - 1) + end + # Returns a string representation of this location. def inspect "#" diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb index 49296117bf5488..6418887147e875 100644 --- a/test/prism/ruby_api_test.rb +++ b/test/prism/ruby_api_test.rb @@ -198,6 +198,17 @@ def test_location_code_units assert_equal 7, location.end_code_units_column(Encoding::UTF_32LE) end + def test_location_chop + location = Prism.parse("foo").value.location + + assert_equal "fo", location.chop.slice + assert_equal "", location.chop.chop.chop.slice + + # Check that we don't go negative. + 10.times { location = location.chop } + assert_equal "", location.slice + end + def test_heredoc? refute parse_expression("\"foo\"").heredoc? refute parse_expression("\"foo \#{1}\"").heredoc? From c5e661b1d720cf5452c3fd5264b73063ca9c4515 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Thu, 11 Apr 2024 10:21:01 -0600 Subject: [PATCH 078/135] [rubygems/rubygems] Fix installing plugins via relative paths This affected both CLI and Gemfile installs https://github.com/rubygems/rubygems/commit/a0d101a8df --- lib/bundler/plugin/installer.rb | 2 +- lib/bundler/plugin/installer/path.rb | 2 +- spec/bundler/plugins/install_spec.rb | 55 +++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 6771f3f1537698..4f60862bb47f64 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -77,7 +77,7 @@ def install_git(names, version, options) def install_path(names, version, path) source_list = SourceList.new - source = source_list.add_path_source({ "path" => path }) + source = source_list.add_path_source({ "path" => path, "root_path" => SharedHelpers.pwd }) install_all_sources(names, version, source_list, source) end diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb index 1b60724b5e4ea3..58a8fa7426b448 100644 --- a/lib/bundler/plugin/installer/path.rb +++ b/lib/bundler/plugin/installer/path.rb @@ -5,7 +5,7 @@ module Plugin class Installer class Path < Bundler::Source::Path def root - Plugin.root + SharedHelpers.in_bundle? ? Bundler.root : Plugin.root end def generate_bin(spec, disable_extensions = false) diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 61c513ed723d76..20c2f1fd2641f3 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -212,14 +212,42 @@ def exec(command, args) end end - it "installs from a path source" do - build_lib "path_plugin" do |s| - s.write "plugins.rb" + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") end - bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" - expect(out).to include("Installed plugin path_plugin") - plugin_should_be_installed("path_plugin") + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" + + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory + end end context "Gemfile eval" do @@ -291,6 +319,21 @@ def exec(command, args) plugin_should_be_installed("ga-plugin") end + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + context "in deployment mode" do it "installs plugins" do install_gemfile <<-G From 842f151d79824cd09d1a9020bc392265b33df79e Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 14:30:13 -0400 Subject: [PATCH 079/135] [PRISM] Enable more passing tests --- prism_compile.c | 2 +- spec/prism.mspec | 8 ++++---- test/.excludes-prism/TestParse.rb | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index fdba3dada05ec4..8acf3515e57a47 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6799,7 +6799,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (popped) PUSH_INSN(ret, location, pop); } else { - rb_raise(rb_eArgError, "Invalid next"); + COMPILE_ERROR(ERROR_ARGS "Invalid next"); return; } } diff --git a/spec/prism.mspec b/spec/prism.mspec index 7279c1e9b1b06c..efa0d931230f72 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -1,5 +1,9 @@ # frozen_string_literal: true +# This is turned off because when we run with --parser=prism we explicitly turn +# off experimental warnings to make sure the output is consistent. +MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") + ## Language MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") @@ -7,7 +11,6 @@ MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding MSpec.register(:exclude, "Hash literal merges multiple nested '**obj' in Hash literals") MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes") MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes and 'key: value' syntax used") -MSpec.register(:exclude, "The next statement in a method is invalid and raises a SyntaxError") MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation") MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation /o") MSpec.register(:exclude, "Regexps with encoding modifiers preserves EUC-JP as /e encoding through interpolation") @@ -25,11 +28,8 @@ MSpec.register(:exclude, "IO.popen with a leading Array argument accepts an IO m MSpec.register(:exclude, "TracePoint#eval_script is the evald source code") MSpec.register(:exclude, "TracePoint#event returns the type of event") MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, method, path and line for a :return event") -MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, path and line for a :class event") MSpec.register(:exclude, "TracePoint.new includes multiple events when multiple event names are passed as params") MSpec.register(:exclude, "TracePoint#path equals \"(eval at __FILE__:__LINE__)\" inside an eval for :end event") -MSpec.register(:exclude, "TracePoint#self return the class object from a class event") -MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") ## Library MSpec.register(:exclude, "Coverage.peek_result returns the result so far") diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index 490a18d035e191..15eced1e020c79 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -1,4 +1,3 @@ -exclude(:test_disallowed_gloal_variable, "unknown") exclude(:test_dynamic_constant_assignment, "unknown") exclude(:test_else_without_rescue, "unknown") exclude(:test_embedded_rd_error, "unknown") From 971b552735f46cbda7345e8de60a2037ee10e259 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Apr 2024 17:31:08 -0700 Subject: [PATCH 080/135] [PRISM] Suppress compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ../prism_compile.c: In function ‘pm_compile_node’: ../compile.c:583:24: warning: ‘retry_end_l’ may be used uninitialized in this function [-Wmaybe-uninitialized] 583 | anchor->last->next = elem; | ~~~~~~~~~~~~~~~~~~~^~~~~~ In file included from ../compile.c:14256: ../prism_compile.c:5796:16: note: ‘retry_end_l’ was declared here 5796 | LABEL *retry_end_l; | ^~~~~~~~~~~ ../compile.c:255:42: warning: ‘retry_label’ may be used uninitialized in this function [-Wmaybe-uninitialized] 255 | #define LABEL_REF(label) ((label)->refcnt++) | ^~ In file included from ../compile.c:14256: ../prism_compile.c:5795:16: note: ‘retry_label’ was declared here 5795 | LABEL *retry_label; | ^~~~~~~~~~~ ../prism_compile.c:5919:52: warning: ‘previous_block’ may be used uninitialized in this function [-Wmaybe-uninitialized] 5919 | ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~ --- prism_compile.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 8acf3515e57a47..2c50c19c1dfb26 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -5791,9 +5791,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_forwarding_super_node_t *cast = (const pm_forwarding_super_node_t *) node; const rb_iseq_t *block = NULL; - const rb_iseq_t *previous_block; - LABEL *retry_label; - LABEL *retry_end_l; + const rb_iseq_t *previous_block = NULL; + LABEL *retry_label = NULL; + LABEL *retry_end_l = NULL; if (cast->block != NULL) { previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; From e36988450e9e9ccccb41c72135f1e57790920668 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Apr 2024 16:07:49 +0900 Subject: [PATCH 081/135] [Bug #20423] Disallow anonymous block within argument forwarding --- parse.y | 2 +- prism/prism.c | 1 - test/prism/fixtures/methods.txt | 2 +- test/prism/snapshots/methods.txt | 17 +++++++++-------- test/ruby/test_compile_prism.rb | 9 --------- test/ruby/test_syntax.rb | 1 + 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/parse.y b/parse.y index 58e39e8f2db94d..3665f9b7c07fd6 100644 --- a/parse.y +++ b/parse.y @@ -4263,7 +4263,7 @@ block_arg : tAMPER arg_value } | tAMPER { - forwarding_arg_check(p, idFWD_BLOCK, 0, "block"); + forwarding_arg_check(p, idFWD_BLOCK, idFWD_ALL, "block"); $$ = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, &@1), &@$); /*% ripper: Qnil %*/ } diff --git a/prism/prism.c b/prism/prism.c index a0ab6d7cee41b4..93328247e0c300 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13486,7 +13486,6 @@ parse_parameters( update_parameter_state(parser, &parser->current, &order); parser_lex(parser); - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL; pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous); diff --git a/test/prism/fixtures/methods.txt b/test/prism/fixtures/methods.txt index 0d2286056fe30d..4bfd976edaa741 100644 --- a/test/prism/fixtures/methods.txt +++ b/test/prism/fixtures/methods.txt @@ -175,7 +175,7 @@ def f x:!a; end def foo x:%(xx); end def foo(...) - bar(&) + bar(...) end def foo(bar = (def baz(bar) = bar; 1)) = 2 diff --git a/test/prism/snapshots/methods.txt b/test/prism/snapshots/methods.txt index 24567d24dfaba7..22580494a46924 100644 --- a/test/prism/snapshots/methods.txt +++ b/test/prism/snapshots/methods.txt @@ -1908,21 +1908,22 @@ │ │ │ @ ForwardingParameterNode (location: (177,8)-(177,11)) │ │ └── block: ∅ │ ├── body: - │ │ @ StatementsNode (location: (178,2)-(178,7)) + │ │ @ StatementsNode (location: (178,2)-(178,10)) │ │ └── body: (length: 1) - │ │ └── @ CallNode (location: (178,2)-(178,7)) + │ │ └── @ CallNode (location: (178,2)-(178,10)) │ │ ├── flags: ignore_visibility │ │ ├── receiver: ∅ │ │ ├── call_operator_loc: ∅ │ │ ├── name: :bar │ │ ├── message_loc: (178,2)-(178,5) = "bar" │ │ ├── opening_loc: (178,5)-(178,6) = "(" - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: (178,7)-(178,8) = ")" - │ │ └── block: - │ │ @ BlockArgumentNode (location: (178,6)-(178,7)) - │ │ ├── expression: ∅ - │ │ └── operator_loc: (178,6)-(178,7) = "&" + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (178,6)-(178,9)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ ForwardingArgumentsNode (location: (178,6)-(178,9)) + │ │ ├── closing_loc: (178,9)-(178,10) = ")" + │ │ └── block: ∅ │ ├── locals: [] │ ├── def_keyword_loc: (177,0)-(177,3) = "def" │ ├── operator_loc: ∅ diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index eef909eb07f27b..d13b150f93e3ac 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -1839,15 +1839,6 @@ def o.bar(&) = foo(&) o.bar { :ok } RUBY - - # Test anonymous block forwarding from argument forwarding - assert_prism_eval(<<~RUBY) - o = Object.new - def o.foo = yield - def o.bar(...) = foo(&) - - o.bar { :ok } - RUBY end def test_BlockLocalVariableNode diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 7cc5e542a7b7bd..44162f06cbdfee 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -190,6 +190,7 @@ def test_argument_forwarding_with_anon_rest_kwrest_and_block assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/) assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/) assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/) end def test_newline_in_block_parameters From 69823b97b517629e5438328e10f758eb6250f762 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Apr 2024 17:02:46 +0900 Subject: [PATCH 082/135] [Bug #20423] Fix error message of prism --- prism/templates/src/diagnostic.c.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index a3aa7902879466..06330c8d5b1a1e 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -99,7 +99,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, From 91c457e0914309a8949f15ee4be4e453951c5985 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 28 Feb 2024 17:46:39 +0900 Subject: [PATCH 083/135] Parenthesized SVN IDs in .mailmap look too verbose [ci skip] --- .mailmap | 452 +++++++++++++++++++++++++++---------------------------- 1 file changed, 226 insertions(+), 226 deletions(-) diff --git a/.mailmap b/.mailmap index 36bef53d644de6..213a0f49164c0f 100644 --- a/.mailmap +++ b/.mailmap @@ -3,429 +3,429 @@ git[bot] git svn # a_matsuda -Akira Matsuda (a_matsuda) -Akira Matsuda (a_matsuda) +Akira Matsuda +Akira Matsuda # aamine -Minero Aoki (aamine) -Minero Aoki (aamine) +Minero Aoki +Minero Aoki # akira -akira yamada (akira) -## akira yamada (akira) -akira yamada (akira) -akira yamada (akira) +akira yamada +## akira yamada +akira yamada +akira yamada # akiyoshi -AKIYOSHI, Masamichi (akiyoshi) -AKIYOSHI, Masamichi (akiyoshi) +AKIYOSHI, Masamichi +AKIYOSHI, Masamichi # akr -Tanaka Akira (akr) -Tanaka Akira (akr) +Tanaka Akira +Tanaka Akira # arai -Koji Arai (arai) -Koji Arai (arai) +Koji Arai +Koji Arai # arton -Akio Tajima (arton) -Akio Tajima (arton) +Akio Tajima +Akio Tajima # aycabta -aycabta (aycabta) -aycabta (aycabta) +aycabta +aycabta # ayumin -Ayumu AIZAWA (ayumin) -Ayumu AIZAWA (ayumin) +Ayumu AIZAWA +Ayumu AIZAWA # azav -Alexander Zavorine (azav) -Alexander Zavorine (azav) +Alexander Zavorine +Alexander Zavorine # charliesome -Charlie Somerville (charliesome) -Charlie Somerville (charliesome) +Charlie Somerville +Charlie Somerville # dave -Dave Thomas (dave) -Dave Thomas (dave) +Dave Thomas +Dave Thomas # davidflanagan -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) +David Flanagan +David Flanagan +David Flanagan +David Flanagan # dblack -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) +David A. Black +David A. Black +David A. Black +David A. Black # drbrain -Eric Hodel (drbrain) -Eric Hodel (drbrain) +Eric Hodel +Eric Hodel # duerst -Martin Dürst (duerst) -Martin Dürst (duerst) +Martin Dürst +Martin Dürst # eban -WATANABE Hirofumi (eban) -WATANABE Hirofumi (eban) +WATANABE Hirofumi +WATANABE Hirofumi # emboss -Martin Bosslet (emboss) -Martin Bosslet (emboss) -Martin Bosslet (emboss) +Martin Bosslet +Martin Bosslet +Martin Bosslet # eregon -Benoit Daloze (eregon) -Benoit Daloze (eregon) +Benoit Daloze +Benoit Daloze # evan -Evan Phoenix (evan) -Evan Phoenix (evan) -Evan Phoenix (evan) +Evan Phoenix +Evan Phoenix +Evan Phoenix # glass -Masaki Matsushita (glass) -Masaki Matsushita (glass) +Masaki Matsushita +Masaki Matsushita # gogotanaka -Kazuki Tanaka (gogotanaka) -Kazuki Tanaka (gogotanaka) +Kazuki Tanaka +Kazuki Tanaka # gotoken -Kentaro Goto (gotoken) -Kentaro Goto (gotoken) +Kentaro Goto +Kentaro Goto # gotoyuzo -GOTOU Yuuzou (gotoyuzo) -GOTOU Yuuzou (gotoyuzo) +GOTOU Yuuzou +GOTOU Yuuzou # gsinclair -Gavin Sinclair (gsinclair) -Gavin Sinclair (gsinclair) +Gavin Sinclair +Gavin Sinclair # H_Konishi -KONISHI Hiromasa (H_Konishi) -KONISHI Hiromasa (H_Konishi) +KONISHI Hiromasa +KONISHI Hiromasa # headius -Charles Oliver Nutter (headius) -Charles Oliver Nutter (headius) +Charles Oliver Nutter +Charles Oliver Nutter # hone -Terence Lee (hone) -Terence Lee (hone) +Terence Lee +Terence Lee # hsbt -Hiroshi SHIBATA (hsbt) -Hiroshi SHIBATA (hsbt) +Hiroshi SHIBATA +Hiroshi SHIBATA # iwamatsu -Nobuhiro Iwamatsu (iwamatsu) -Nobuhiro Iwamatsu (iwamatsu) +Nobuhiro Iwamatsu +Nobuhiro Iwamatsu # jeg2 -James Edward Gray II (jeg2) -James Edward Gray II (jeg2) +James Edward Gray II +James Edward Gray II # jim -Jim Weirich (jim) -Jim Weirich (jim) +Jim Weirich +Jim Weirich # k0kubun -Takashi Kokubun (k0kubun) -Takashi Kokubun (k0kubun) +Takashi Kokubun +Takashi Kokubun # kanemoto -Yutaka Kanemoto (kanemoto) -Yutaka Kanemoto (kanemoto) +Yutaka Kanemoto +Yutaka Kanemoto # katsu -UENO Katsuhiro (katsu) -UENO Katsuhiro (katsu) +UENO Katsuhiro +UENO Katsuhiro # kazu -Kazuhiro NISHIYAMA (kazu) -Kazuhiro NISHIYAMA (kazu) +Kazuhiro NISHIYAMA +Kazuhiro NISHIYAMA # keiju -Keiju Ishitsuka (keiju) -Keiju Ishitsuka (keiju) +Keiju Ishitsuka +Keiju Ishitsuka # knu -Akinori MUSHA (knu) -Akinori MUSHA (knu) +Akinori MUSHA +Akinori MUSHA # ko1 -Koichi Sasada (ko1) -Koichi Sasada (ko1) +Koichi Sasada +Koichi Sasada # kosaki -KOSAKI Motohiro (kosaki) -KOSAKI Motohiro (kosaki) +KOSAKI Motohiro +KOSAKI Motohiro # kosako -K.Kosako (kosako) -K.Kosako (kosako) +K.Kosako +K.Kosako # kou -Sutou Kouhei (kou) -Sutou Kouhei (kou) -Sutou Kouhei (kou) +Sutou Kouhei +Sutou Kouhei +Sutou Kouhei # kouji -Kouji Takao (kouji) -Kouji Takao (kouji) -Kouji Takao (kouji) +Kouji Takao +Kouji Takao +Kouji Takao # ksaito -Kazuo Saito (ksaito) -Kazuo Saito (ksaito) +Kazuo Saito +Kazuo Saito # ktsj -Kazuki Tsujimoto (ktsj) -Kazuki Tsujimoto (ktsj) +Kazuki Tsujimoto +Kazuki Tsujimoto # luislavena -Luis Lavena (luislavena) -Luis Lavena (luislavena) +Luis Lavena +Luis Lavena # mame -Yusuke Endoh (mame) -## Yusuke Endoh (mame) -Yusuke Endoh (mame) +Yusuke Endoh +## Yusuke Endoh +Yusuke Endoh # marcandre -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) +Marc-Andre Lafortune +Marc-Andre Lafortune +Marc-Andre Lafortune # matz -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto # michal -Michal Rokos (michal) -Michal Rokos (michal) +Michal Rokos +Michal Rokos # mneumann -Michael Neumann (mneumann) -Michael Neumann (mneumann) +Michael Neumann +Michael Neumann # mrkn -Kenta Murata (mrkn) -Kenta Murata (mrkn) -Kenta Murata (mrkn) <3959+mrkn@users.noreply.github.com> -Kenta Murata (mrkn) +Kenta Murata +Kenta Murata +Kenta Murata <3959+mrkn@users.noreply.github.com> +Kenta Murata # nagachika -nagachika (nagachika) -nagachika (nagachika) +nagachika +nagachika # nagai -Hidetoshi NAGAI (nagai) -Hidetoshi NAGAI (nagai) +Hidetoshi NAGAI +Hidetoshi NAGAI # nahi -Hiroshi Nakamura (nahi) -Hiroshi Nakamura (nahi) +Hiroshi Nakamura +Hiroshi Nakamura # nari -Narihiro Nakamura (nari) -Narihiro Nakamura (nari) +Narihiro Nakamura +Narihiro Nakamura # naruse -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) +NARUSE, Yui +NARUSE, Yui +NARUSE, Yui # ngoto -Naohisa Goto (ngoto) -Naohisa Goto (ngoto) +Naohisa Goto +Naohisa Goto # nobu -Nobuyoshi Nakada (nobu) -Nobuyoshi Nakada (nobu) +Nobuyoshi Nakada +Nobuyoshi Nakada # normal -Eric Wong (normal) -Eric Wong (normal) -Eric Wong (normal) +Eric Wong +Eric Wong +Eric Wong # ntalbott -Nathaniel Talbott (ntalbott) -Nathaniel Talbott (ntalbott) +Nathaniel Talbott +Nathaniel Talbott # ocean -Hirokazu Yamamoto (ocean) -Hirokazu Yamamoto (ocean) +Hirokazu Yamamoto +Hirokazu Yamamoto # odaira -Rei Odaira (odaira) -Rei Odaira (odaira) -Rei Odaira (odaira) +Rei Odaira +Rei Odaira +Rei Odaira # okkez -okkez (okkez) -okkez (okkez) +okkez +okkez # rhe -Kazuki Yamaguchi (rhe) -Kazuki Yamaguchi (rhe) +Kazuki Yamaguchi +Kazuki Yamaguchi # ryan -Ryan Davis (ryan) -Ryan Davis (ryan) -Ryan Davis (ryan) +Ryan Davis +Ryan Davis +Ryan Davis # samuel -Samuel Williams (samuel) -Samuel Williams (samuel) +Samuel Williams +Samuel Williams # seki -Masatoshi SEKI (seki) -Masatoshi SEKI (seki) +Masatoshi SEKI +Masatoshi SEKI # ser -Sean Russell (ser) -Sean Russell (ser) +Sean Russell +Sean Russell # shigek -Shigeo Kobayashi (shigek) -Shigeo Kobayashi (shigek) +Shigeo Kobayashi +Shigeo Kobayashi # shirosaki -Hiroshi Shirosaki (shirosaki) -Hiroshi Shirosaki (shirosaki) +Hiroshi Shirosaki +Hiroshi Shirosaki # sho-h -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto # shugo -Shugo Maeda (shugo) -Shugo Maeda (shugo) +Shugo Maeda +Shugo Maeda # shyouhei -卜部昌平 (shyouhei) -卜部昌平 (shyouhei) +卜部昌平 +卜部昌平 # siena -Siena. (siena) -Siena. (siena) +Siena. +Siena. # sonots -sonots (sonots) -sonots (sonots) +sonots +sonots # sorah -Sorah Fukumori (sorah) -Sorah Fukumori (sorah) +Sorah Fukumori +Sorah Fukumori # stomar -Marcus Stollsteimer (stomar) -Marcus Stollsteimer (stomar) +Marcus Stollsteimer +Marcus Stollsteimer # suke -Masaki Suketa (suke) -Masaki Suketa (suke) +Masaki Suketa +Masaki Suketa # tadd -Tadashi Saito (tadd) -Tadashi Saito (tadd) +Tadashi Saito +Tadashi Saito # tadf -Tadayoshi Funaba (tadf) -Tadayoshi Funaba (tadf) +Tadayoshi Funaba +Tadayoshi Funaba # takano32 -TAKANO Mitsuhiro (takano32) -TAKANO Mitsuhiro (takano32) +TAKANO Mitsuhiro +TAKANO Mitsuhiro # tarui -Masaya Tarui (tarui) -Masaya Tarui (tarui) +Masaya Tarui +Masaya Tarui # technorama -Technorama Ltd. (technorama) -Technorama Ltd. (technorama) +Technorama Ltd. +Technorama Ltd. # tenderlove -Aaron Patterson (tenderlove) -Aaron Patterson (tenderlove) +Aaron Patterson +Aaron Patterson # tmm1 -Aman Gupta (tmm1) -Aman Gupta (tmm1) +Aman Gupta +Aman Gupta # ts -Guy Decoux (ts) -Guy Decoux (ts) +Guy Decoux +Guy Decoux # ttate -Takaaki Tateishi (ttate) -## Takaaki Tateishi (ttate) -Takaaki Tateishi (ttate) +Takaaki Tateishi +## Takaaki Tateishi +Takaaki Tateishi # uema2 -Takaaki Uematsu (uema2) -Takaaki Uematsu (uema2) +Takaaki Uematsu +Takaaki Uematsu # usa -U.Nakamura (usa) -U.Nakamura (usa) -U.Nakamura (usa) +U.Nakamura +U.Nakamura +U.Nakamura # wakou -Wakou Aoyama (wakou) -Wakou Aoyama (wakou) +Wakou Aoyama +Wakou Aoyama # wanabe -wanabe (wanabe) -wanabe (wanabe) +wanabe +wanabe # watson1978 -Watson (watson1978) -Watson (watson1978) +Watson +Watson # wew -William Webber (wew) -William Webber (wew) +William Webber +William Webber # why -why the lucky stiff (why) -why the lucky stiff (why) +why the lucky stiff +why the lucky stiff # xibbar -Takeyuki FUJIOKA (xibbar) -Takeyuki FUJIOKA (xibbar) +Takeyuki FUJIOKA +Takeyuki FUJIOKA # yugui -Yuki Yugui Sonoda (yugui) -Yuki Yugui Sonoda (yugui) +Yuki Yugui Sonoda +Yuki Yugui Sonoda # yui-knk -yui-knk (yui-knk) -yui-knk (yui-knk) +yui-knk +yui-knk # yuki -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) +Yuki Nishijima +Yuki Nishijima +Yuki Nishijima # zsombor -Dee Zsombor (zsombor) -Dee Zsombor (zsombor) +Dee Zsombor +Dee Zsombor # zzak -zzak (zzak) -zzak (zzak) +zzak +zzak From 7b8b936f4a1cd9a629c0465c287fd0ed40519ebe Mon Sep 17 00:00:00 2001 From: Satoshi Tagomori Date: Fri, 12 Apr 2024 18:31:22 +0900 Subject: [PATCH 084/135] [DOC] Fix the wrong comment This function checks the CL's superclasses containing the class C at the end of it. That means C is a superclass of CL, not a subclass. --- object.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/object.c b/object.c index 4673ba9f6965ce..54240f0774ba81 100644 --- a/object.c +++ b/object.c @@ -869,7 +869,7 @@ rb_obj_is_instance_of(VALUE obj, VALUE c) return RBOOL(rb_obj_class(obj) == c); } -// Returns whether c is a proper (c != cl) subclass of cl +// Returns whether c is a proper (c != cl) superclass of cl // Both c and cl must be T_CLASS static VALUE class_search_class_ancestor(VALUE cl, VALUE c) @@ -882,7 +882,7 @@ class_search_class_ancestor(VALUE cl, VALUE c) VALUE *classes = RCLASS_SUPERCLASSES(cl); // If c's inheritance chain is longer, it cannot be an ancestor - // We are checking for a proper subclass so don't check if they are equal + // We are checking for a proper superclass so don't check if they are equal if (cl_depth <= c_depth) return Qfalse; From f1d9e895b92953add4b12f477c27852cc3d0955b Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 12 Apr 2024 20:00:58 +0800 Subject: [PATCH 085/135] [ruby/irb] Pass statements to Context#evaluate (https://github.com/ruby/irb/pull/920) This has a few benefits: - We can keep hiding the evaluation logic inside the Context level, which has always been the convention until #824 was merged recently. - Although not an official API, gems like `debug` and `mission_control-jobs` patch `Context#evaluate` to wrap their own logic around it. This implicit contract was broken after #824, and this change restores it. In addition to the refactor, I also converted some context-level evaluation tests into integration tests, which are more robust and easier to maintain. https://github.com/ruby/irb/commit/b32aee4068 --- lib/irb.rb | 16 +------------ lib/irb/context.rb | 25 +++++++++++++++----- test/irb/test_context.rb | 41 +++++--------------------------- test/irb/test_irb.rb | 50 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 99fd1c5df01bb8..723035f15ac4c3 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1028,21 +1028,7 @@ def eval_input return statement.code end - case statement - when Statement::EmptyInput - # Do nothing - when Statement::Expression - @context.evaluate(statement.code, line_no) - when Statement::Command - ret = statement.command_class.execute(@context, statement.arg) - # TODO: Remove this output once we have a better way to handle it - # This is to notify `debug`'s test framework that the current input has been processed - # We also need to have a way to restart/stop threads around command execution - # when being used as `debug`'s console. - # https://github.com/ruby/debug/blob/master/lib/debug/irb_integration.rb#L8-L13 - puts "INTERNAL_INFO: {}" if @context.with_debugger && ENV['RUBY_DEBUG_TEST_UI'] == 'terminal' - @context.set_last_value(ret) - end + @context.evaluate(statement, line_no) if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? diff --git a/lib/irb/context.rb b/lib/irb/context.rb index e3c41924595a29..836b8d26257473 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -585,31 +585,44 @@ def inspect_mode=(opt) @inspect_mode end - def evaluate(line, line_no) # :nodoc: + def evaluate(statement, line_no) # :nodoc: @line_no = line_no result = nil + case statement + when Statement::EmptyInput + return + when Statement::Expression + result = evaluate_expression(statement.code, line_no) + when Statement::Command + result = statement.command_class.execute(self, statement.arg) + end + + set_last_value(result) + end + + def evaluate_expression(code, line_no) # :nodoc: + result = nil if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? IRB.set_measure_callback end if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item proc do - callback.(self, line, line_no, arg) do + callback.(self, code, line_no, arg) do chain.call end end end.call else - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end - - set_last_value(result) + result end def inspect_last_value # :nodoc: diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 5812ea041edb63..aff4b5b67c0eaa 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -28,35 +28,6 @@ def teardown restore_encodings end - def test_last_value - assert_nil(@context.last_value) - assert_nil(@context.evaluate('_', 1)) - obj = Object.new - @context.set_last_value(obj) - assert_same(obj, @context.last_value) - assert_same(obj, @context.evaluate('_', 1)) - end - - def test_evaluate_with_encoding_error_without_lineno - if RUBY_ENGINE == 'truffleruby' - omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" - end - - if RUBY_VERSION >= "3.4." - omit "Now raises SyntaxError" - end - - assert_raise_with_message(EncodingError, /invalid symbol/) { - @context.evaluate(%q[:"\xAE"], 1) - # The backtrace of this invalid encoding hash doesn't contain lineno. - } - end - - def test_evaluate_still_emits_warning - assert_warning("(irb):1: warning: END in method; use at_exit\n") do - @context.evaluate(%q[def foo; END {}; end], 1) - end - end def test_eval_input verbose, $VERBOSE = $VERBOSE, nil @@ -382,7 +353,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -392,7 +363,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -402,7 +373,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -412,7 +383,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -422,7 +393,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -432,7 +403,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) end end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 966c840135c74f..84b9ee36441d21 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -42,6 +42,56 @@ class Foo assert_include output, "From: #{@ruby_file.path}:1" end + def test_underscore_stores_last_result + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "1 + 1" + type "_ + 10" + type "exit!" + end + + assert_include output, "=> 12" + end + + def test_evaluate_with_encoding_error_without_lineno + if RUBY_ENGINE == 'truffleruby' + omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" + end + + if RUBY_VERSION >= "3.4." + omit "Now raises SyntaxError" + end + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[:"\xAE"] + type "exit!" + end + + assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"' + # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately + assert_include output, "EncodingError" + end + + def test_evaluate_still_emits_warning + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[def foo; END {}; end] + type "exit!" + end + + assert_include output, '(irb):1: warning: END in method; use at_exit' + end + def test_symbol_aliases_dont_affect_ruby_syntax write_ruby <<~'RUBY' $foo = "It's a foo" From c4b5f3f1422b1c83bc4c7e03d32218881c0b6945 Mon Sep 17 00:00:00 2001 From: David Marshall Date: Thu, 4 Apr 2024 12:33:03 -0500 Subject: [PATCH 086/135] [rubygems/rubygems] bundler CLI option for add gem --glob= Bundler online documentation says that if the gem is located within a subdirectory of a git repository, you can use the `:glob` option to specify the location of its .gemspec `gem 'cf-copilot', git: 'https://github.com/cloudfoundry/copilot', glob: 'sdk/ruby/*.gemspec'` This change allows for equivalent functionality from the bundler CLI `bundle add cf-copilot --git=https://github.com/cloudfoundry/copilot --glob=sdk/ruby/*.gemspec` https://github.com/rubygems/rubygems/commit/91052e5868 --- lib/bundler/cli.rb | 1 + lib/bundler/dependency.rb | 3 +- lib/bundler/injector.rb | 3 +- spec/bundler/commands/add_spec.rb | 55 +++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 36405367620b7b..6ee86b182f7431 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -347,6 +347,7 @@ def binstubs(*gems) method_option "github", type: :string method_option "branch", type: :string method_option "ref", type: :string + method_option "glob", type: :string method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it" method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem" method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem" diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 77d7a00362b931..2a4f72fe55a305 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -7,7 +7,7 @@ module Bundler class Dependency < Gem::Dependency attr_reader :autorequire - attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref + attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :glob ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..34).to_a).freeze PLATFORM_MAP = { @@ -39,6 +39,7 @@ def initialize(name, version, options = {}, &blk) @github = options["github"] @branch = options["branch"] @ref = options["ref"] + @glob = options["glob"] @platforms = Array(options["platforms"]) @env = options["env"] @should_include = options.fetch("should_include", true) diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index cf561c2ee49de3..879b481339281e 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -120,9 +120,10 @@ def build_gem_lines(conservative_versioning) github = ", :github => \"#{d.github}\"" unless d.github.nil? branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil? ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil? + glob = ", :glob => \"#{d.glob}\"" unless d.glob.nil? require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil? - %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{require_path}) + %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path}) end.join("\n") end diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index e2f5bbf42fad0e..93d5ea7239bc36 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -175,6 +175,61 @@ end end + describe "with --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --ref and --glob" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --github and --glob" do + it "adds dependency with specified github source", :realworld do + bundle "add rake --github=ruby/rake --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --branch --and glob" do + it "adds dependency with specified github source and branch", :realworld do + bundle "add rake --github=ruby/rake --branch=master --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --ref and --glob" do + it "adds dependency with specified github source and ref", :realworld do + bundle "add rake --github=ruby/rake --ref=5c60da8 --glob=./*.gemspec" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" From a64a42ae386576cb91f5fb017b13a8f8a77077a4 Mon Sep 17 00:00:00 2001 From: David Marshall Date: Fri, 5 Apr 2024 08:53:13 -0500 Subject: [PATCH 087/135] [rubygems/rubygems] `bundle add --glob` continued- quote glob value invocation in specs, add banner text for CLI recommending single quotes https://github.com/rubygems/rubygems/commit/6d2cf955f9 --- lib/bundler/cli.rb | 2 +- spec/bundler/commands/add_spec.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 6ee86b182f7431..8db725bbde9f99 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -347,7 +347,7 @@ def binstubs(*gems) method_option "github", type: :string method_option "branch", type: :string method_option "ref", type: :string - method_option "glob", type: :string + method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)" method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it" method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem" method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem" diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 93d5ea7239bc36..36e286793b6d73 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -177,7 +177,7 @@ describe "with --git and --glob" do it "adds dependency with specified git source" do - bundle "add foo --git=#{lib_path("foo-2.0")} --glob=./*.gemspec" + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :glob => "\./\*\.gemspec"}) expect(the_bundle).to include_gems "foo 2.0" @@ -190,7 +190,7 @@ end it "adds dependency with specified git source and branch" do - bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob=./*.gemspec" + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test", :glob => "\./\*\.gemspec"}) expect(the_bundle).to include_gems "foo 2.0" @@ -199,7 +199,7 @@ describe "with --git and --ref and --glob" do it "adds dependency with specified git source and branch" do - bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob=./*.gemspec" + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}", :glob => "\./\*\.gemspec"}) expect(the_bundle).to include_gems "foo 2.0" @@ -208,7 +208,7 @@ describe "with --github and --glob" do it "adds dependency with specified github source", :realworld do - bundle "add rake --github=ruby/rake --glob=./*.gemspec" + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) end @@ -216,7 +216,7 @@ describe "with --github and --branch --and glob" do it "adds dependency with specified github source and branch", :realworld do - bundle "add rake --github=ruby/rake --branch=master --glob=./*.gemspec" + bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) end @@ -224,7 +224,7 @@ describe "with --github and --ref and --glob" do it "adds dependency with specified github source and ref", :realworld do - bundle "add rake --github=ruby/rake --ref=5c60da8 --glob=./*.gemspec" + bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) end From abd05c848f437405e10410ded2a3d666e1b9bba5 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 09:52:29 -0400 Subject: [PATCH 088/135] Sync latest prism --- prism/templates/src/diagnostic.c.erb | 2 +- test/prism/parser_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 06330c8d5b1a1e..957df1946cee45 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -99,7 +99,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&`; no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb index 237a4397cae7ae..d60def5a478ba6 100644 --- a/test/prism/parser_test.rb +++ b/test/prism/parser_test.rb @@ -74,6 +74,7 @@ class ParserTest < TestCase "comments.txt", "heredoc_with_comment.txt", "indented_file_end.txt", + "methods.txt", "strings.txt", "xstring_with_backslash.txt" ] From 0924ff2d390084922fbcebada20b968fa3e8238c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 2 Apr 2024 21:41:40 +0100 Subject: [PATCH 089/135] [ruby/prism] Fix parser translation's heredoc whitespace calculation Given this example: ```rb <<~HEREDOC #{x} HEREDOC ``` Both the parser gem and Prism's translation layer would generate the following AST: ``` s(:dstr, s(:begin, s(:int, 1)), s(:str, " a\n")) ``` However, the parser gem inserts a empty string node into this node's location, like: ``` , @heredoc_body=#, @heredoc_end=#, @node=s(:dstr, s(:str, ""), s(:begin, s(:int, 1)), s(:str, " a\n"))> ``` This is required to calculate the correct whitespace for the heredoc body. We need to adjust the translation layer to account for this. With this fix, we also won't need to ignore the tilde heredoc fixture anymore. https://github.com/ruby/prism/commit/e7372e3ba5 --- lib/prism/translation/parser/compiler.rb | 29 +++++++++++++++++++++++- test/prism/parser_test.rb | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 0b1893cade9a4e..9437589623f26b 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -947,8 +947,35 @@ def visit_interpolated_regular_expression_node(node) def visit_interpolated_string_node(node) if node.heredoc? children, closing = visit_heredoc(node) + opening = token(node.opening_loc) + + start_offset = node.opening_loc.end_offset + 1 + end_offset = node.parts.first.location.start_offset + + # In the below case, the offsets should be the same: + # + # <<~HEREDOC + # a #{b} + # HEREDOC + # + # But in this case, the end_offset would be greater than the start_offset: + # + # <<~HEREDOC + # #{b} + # HEREDOC + # + # So we need to make sure the result node's heredoc range is correct, without updating the children + result = if start_offset < end_offset + # We need to add a padding string to ensure that the heredoc has correct range for its body + padding_string_node = builder.string_internal(["", srange_offsets(start_offset, end_offset)]) + node_with_correct_location = builder.string_compose(opening, [padding_string_node, *children], closing) + # But the padding string should not be included in the final AST, so we need to update the result's children + node_with_correct_location.updated(:dstr, children) + else + builder.string_compose(opening, children, closing) + end - return builder.string_compose(token(node.opening_loc), children, closing) + return result end parts = if node.parts.one? { |part| part.type == :string_node } diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb index d60def5a478ba6..79b65cf75b41cc 100644 --- a/test/prism/parser_test.rb +++ b/test/prism/parser_test.rb @@ -59,7 +59,6 @@ class ParserTest < TestCase "regex_char_width.txt", "spanning_heredoc.txt", "spanning_heredoc_newlines.txt", - "tilde_heredocs.txt", "unescaping.txt" ] @@ -76,6 +75,7 @@ class ParserTest < TestCase "indented_file_end.txt", "methods.txt", "strings.txt", + "tilde_heredocs.txt", "xstring_with_backslash.txt" ] From 4fc457e2b7fcaf78294d3315125926fb2e580f6d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 11 Apr 2024 15:54:40 -0400 Subject: [PATCH 090/135] [ruby/prism] Implement the void statement warning https://github.com/ruby/prism/commit/802ff71cd4 --- prism/config.yml | 1 + prism/prism.c | 166 ++++++++++++++++++++++++++- prism/templates/src/diagnostic.c.erb | 3 +- test/prism/warnings_test.rb | 72 +++++++++++- 4 files changed, 234 insertions(+), 8 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 6341233f83ec42..889ceccbf4e84d 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -270,6 +270,7 @@ warnings: - SHEBANG_CARRIAGE_RETURN - UNEXPECTED_CARRIAGE_RETURN - UNUSED_LOCAL_VARIABLE + - VOID_STATEMENT tokens: - name: EOF value: 1 diff --git a/prism/prism.c b/prism/prism.c index 93328247e0c300..3141b2749c767d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1182,6 +1182,158 @@ pm_assert_value_expression(pm_parser_t *parser, pm_node_t *node) { } } +/** + * Warn if the given node is a "void" statement. + */ +static void +pm_void_statement_check(pm_parser_t *parser, const pm_node_t *node) { + const char *type = NULL; + int length = 0; + + switch (PM_NODE_TYPE(node)) { + case PM_BACK_REFERENCE_READ_NODE: + case PM_CLASS_VARIABLE_READ_NODE: + case PM_GLOBAL_VARIABLE_READ_NODE: + case PM_INSTANCE_VARIABLE_READ_NODE: + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_NUMBERED_REFERENCE_READ_NODE: + type = "a variable"; + length = 10; + break; + case PM_CALL_NODE: { + const pm_call_node_t *cast = (const pm_call_node_t *) node; + if (cast->call_operator_loc.start != NULL || cast->message_loc.start == NULL) break; + + const pm_constant_t *message = pm_constant_pool_id_to_constant(&parser->constant_pool, cast->name); + switch (message->length) { + case 1: + switch (message->start[0]) { + case '+': + case '-': + case '*': + case '/': + case '%': + case '|': + case '^': + case '&': + case '>': + case '<': + type = (const char *) message->start; + length = 1; + break; + } + break; + case 2: + switch (message->start[1]) { + case '=': + if (message->start[0] == '<' || message->start[0] == '>' || message->start[0] == '!' || message->start[0] == '=') { + type = (const char *) message->start; + length = 2; + } + break; + case '@': + if (message->start[0] == '+' || message->start[0] == '-') { + type = (const char *) message->start; + length = 2; + } + break; + case '*': + if (message->start[0] == '*') { + type = (const char *) message->start; + length = 2; + } + break; + } + break; + case 3: + if (memcmp(message->start, "<=>", 3) == 0) { + type = "<=>"; + length = 3; + } + break; + } + + break; + } + case PM_CONSTANT_PATH_NODE: + type = "::"; + length = 2; + break; + case PM_CONSTANT_READ_NODE: + type = "a constant"; + length = 10; + break; + case PM_DEFINED_NODE: + type = "defined?"; + length = 8; + break; + case PM_FALSE_NODE: + type = "false"; + length = 5; + break; + case PM_FLOAT_NODE: + case PM_IMAGINARY_NODE: + case PM_INTEGER_NODE: + case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: + case PM_INTERPOLATED_STRING_NODE: + case PM_RATIONAL_NODE: + case PM_REGULAR_EXPRESSION_NODE: + case PM_SOURCE_ENCODING_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_STRING_NODE: + case PM_SYMBOL_NODE: + type = "a literal"; + length = 9; + break; + case PM_NIL_NODE: + type = "nil"; + length = 3; + break; + case PM_RANGE_NODE: { + const pm_range_node_t *cast = (const pm_range_node_t *) node; + + if (PM_NODE_FLAG_P(cast, PM_RANGE_FLAGS_EXCLUDE_END)) { + type = "..."; + length = 3; + } else { + type = ".."; + length = 2; + } + + break; + } + case PM_SELF_NODE: + type = "self"; + length = 4; + break; + case PM_TRUE_NODE: + type = "true"; + length = 4; + break; + default: + break; + } + + if (type != NULL) { + PM_PARSER_WARN_NODE_FORMAT(parser, node, PM_WARN_VOID_STATEMENT, length, type); + } +} + +/** + * Warn if any of the statements that are not the last statement in the list are + * a "void" statement. + */ +static void +pm_void_statements_check(pm_parser_t *parser, const pm_statements_node_t *node) { + if (parser->parsing_eval) return; + + assert(node->body.size > 0); + for (size_t index = 0; index < node->body.size - 1; index++) { + pm_void_statement_check(parser, node->body.nodes[index]); + } +} + /** * When we're handling the predicate of a conditional, we need to know our * context in order to determine the kind of warning we should deliver to the @@ -12911,6 +13063,8 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { } context_pop(parser); + pm_void_statements_check(parser, statements); + return statements; } @@ -16666,6 +16820,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pop_block_exits(parser, previous_block_exits); pm_node_list_free(¤t_block_exits); + pm_void_statements_check(parser, statements); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } case PM_TOKEN_BRACE_LEFT: { @@ -20137,8 +20292,15 @@ parse_program(pm_parser_t *parser) { parser_lex(parser); pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_MAIN); - if (!statements) { + + if (statements == NULL) { statements = pm_statements_node_create(parser); + } else if (!parser->parsing_eval) { + // If we have statements, then the top-level statement should be + // explicitly checked as well. We have to do this here because + // everywhere else we check all but the last statement. + assert(statements->body.size > 0); + pm_void_statement_check(parser, statements->body.nodes[statements->body.size - 1]); } pm_constant_id_list_t locals; @@ -20589,7 +20751,7 @@ pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { pm_node_t *node = pm_parse(&parser); pm_node_destroy(&parser, node); - bool result = parser.error_list.size == 0 && parser.warning_list.size == 0; + bool result = parser.error_list.size == 0; pm_parser_free(&parser); pm_options_free(&options); diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 957df1946cee45..5044a69c2b05b1 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -352,7 +352,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_WARN_LITERAL_IN_CONDITION_VERBOSE] = { "%sliteral in %s", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_SHEBANG_CARRIAGE_RETURN] = { "shebang line ending with \\r may cause problems", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_UNEXPECTED_CARRIAGE_RETURN] = { "encountered \\r in middle of line, treated as a mere space", PM_WARNING_LEVEL_DEFAULT }, - [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE } + [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_VOID_STATEMENT] = { "possibly useless use of %.*s in void context", PM_WARNING_LEVEL_VERBOSE } }; /** diff --git a/test/prism/warnings_test.rb b/test/prism/warnings_test.rb index 5f1e746b525927..ff9c306c9900be 100644 --- a/test/prism/warnings_test.rb +++ b/test/prism/warnings_test.rb @@ -24,15 +24,15 @@ def test_ambiguous_regexp end def test_equal_in_conditional - assert_warning("if a = 1; end; a", "should be ==") + assert_warning("if a = 1; end; a = a", "should be ==") end def test_dot_dot_dot_eol - assert_warning("foo...", "... at EOL") + assert_warning("_ = foo...", "... at EOL") assert_warning("def foo(...) = bar ...", "... at EOL") - assert_warning("foo... #", "... at EOL") - assert_warning("foo... \t\v\f\n", "... at EOL") + assert_warning("_ = foo... #", "... at EOL") + assert_warning("_ = foo... \t\v\f\n", "... at EOL") refute_warning("p foo...bar") refute_warning("p foo... bar") @@ -51,7 +51,7 @@ def test_duplicated_when_clause end def test_float_out_of_range - assert_warning("1.0e100000", "out of range") + assert_warning("_ = 1.0e100000", "out of range") end def test_integer_in_flip_flop @@ -125,6 +125,68 @@ def test_unused_local_variables refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end") end + def test_void_statements + assert_warning("foo = 1; foo", "a variable in void") + assert_warning("@foo", "a variable in void") + assert_warning("@@foo", "a variable in void") + assert_warning("$foo", "a variable in void") + assert_warning("$+", "a variable in void") + assert_warning("$1", "a variable in void") + + assert_warning("self", "self in void") + assert_warning("nil", "nil in void") + assert_warning("true", "true in void") + assert_warning("false", "false in void") + + assert_warning("1", "literal in void") + assert_warning("1.0", "literal in void") + assert_warning("1r", "literal in void") + assert_warning("1i", "literal in void") + assert_warning(":foo", "literal in void") + assert_warning("\"foo\"", "literal in void") + assert_warning("\"foo\#{1}\"", "literal in void") + assert_warning("/foo/", "literal in void") + assert_warning("/foo\#{1}/", "literal in void") + + assert_warning("Foo", "constant in void") + assert_warning("::Foo", ":: in void") + assert_warning("Foo::Bar", ":: in void") + + assert_warning("1..2", ".. in void") + assert_warning("1..", ".. in void") + assert_warning("..2", ".. in void") + assert_warning("1...2", "... in void") + assert_warning("1...;", "... in void") + assert_warning("...2", "... in void") + + assert_warning("defined?(foo)", "defined? in void") + + assert_warning("1 + 1", "+ in void") + assert_warning("1 - 1", "- in void") + assert_warning("1 * 1", "* in void") + assert_warning("1 / 1", "/ in void") + assert_warning("1 % 1", "% in void") + assert_warning("1 | 1", "| in void") + assert_warning("1 ^ 1", "^ in void") + assert_warning("1 & 1", "& in void") + assert_warning("1 > 1", "> in void") + assert_warning("1 < 1", "< in void") + + assert_warning("1 ** 1", "** in void") + assert_warning("1 <= 1", "<= in void") + assert_warning("1 >= 1", ">= in void") + assert_warning("1 != 1", "!= in void") + assert_warning("1 == 1", "== in void") + assert_warning("1 <=> 1", "<=> in void") + + assert_warning("+foo", "+@ in void") + assert_warning("-foo", "-@ in void") + + assert_warning("def foo; @bar; @baz; end", "variable in void") + refute_warning("def foo; @bar; end") + refute_warning("@foo", compare: false, scopes: [[]]) + end + private def assert_warning(source, message) From 8e514bedf983a8a0a2f358709823fabfc74407d7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 10:01:08 -0400 Subject: [PATCH 091/135] [ruby/prism] Remove outdated comment https://github.com/ruby/prism/commit/9adc88ddf8 --- prism/prism.c | 1 - 1 file changed, 1 deletion(-) diff --git a/prism/prism.c b/prism/prism.c index 3141b2749c767d..edf507304fdc3e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13297,7 +13297,6 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for if (token_begins_expression_p(parser->current.type)) { expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_ARGUMENT); } else { - // A block forwarding in a method having `...` parameter (e.g. `def foo(...); bar(&); end`) is available. pm_parser_scope_forwarding_block_check(parser, &operator); } From 5f2bcbb6d4476fcfc278d612e34a483f7592a581 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 10:29:39 -0400 Subject: [PATCH 092/135] [PRISM] Ensure no void warnings in rubyoptions test --- test/ruby/test_rubyoptions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 8ea42251394c97..76be9152a7eee8 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -306,7 +306,7 @@ def test_parser_flag assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), warning) assert_in_out_err(%w(--parser=prism -W:no-experimental -e) + ["puts :hi"], "", %w(hi), []) - assert_in_out_err(%w(--parser=prism -W:no-experimental --dump=parsetree -e :hi), "", /"hi"/, []) + assert_in_out_err(%w(--parser=prism -W:no-experimental --dump=parsetree -e _=:hi), "", /"hi"/, []) assert_in_out_err(%w(--parser=parse.y -e) + ["puts :hi"], "", %w(hi), []) assert_norun_with_rflag('--parser=parse.y', '--version', "") From c553d3483542f933df41a71f1ad8f5bcb1142c2b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 10:22:13 -0400 Subject: [PATCH 093/135] [ruby/prism] Warn void regardless of eval https://github.com/ruby/prism/commit/48ba434fa4 --- prism/prism.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index edf507304fdc3e..694efddb2f7a31 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1326,8 +1326,6 @@ pm_void_statement_check(pm_parser_t *parser, const pm_node_t *node) { */ static void pm_void_statements_check(pm_parser_t *parser, const pm_statements_node_t *node) { - if (parser->parsing_eval) return; - assert(node->body.size > 0); for (size_t index = 0; index < node->body.size - 1; index++) { pm_void_statement_check(parser, node->body.nodes[index]); From c41ecf3f470ab5a4cba410743dc8154694f9d885 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 10:49:30 -0400 Subject: [PATCH 094/135] [ruby/prism] Create the warning for unreachable statements https://github.com/ruby/prism/commit/e17c86b886 --- prism/config.yml | 1 + prism/prism.c | 47 +++++++++++++++++++--------- prism/templates/src/diagnostic.c.erb | 1 + test/prism/warnings_test.rb | 14 +++++++++ 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 889ceccbf4e84d..17fff0431a7cf9 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -269,6 +269,7 @@ warnings: - LITERAL_IN_CONDITION_VERBOSE - SHEBANG_CARRIAGE_RETURN - UNEXPECTED_CARRIAGE_RETURN + - UNREACHABLE_STATEMENT - UNUSED_LOCAL_VARIABLE - VOID_STATEMENT tokens: diff --git a/prism/prism.c b/prism/prism.c index 694efddb2f7a31..c370a8b2bf672a 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1895,7 +1895,7 @@ static pm_statements_node_t * pm_statements_node_create(pm_parser_t *parser); static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement); +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement); static size_t pm_statements_node_body_length(pm_statements_node_t *node); @@ -4554,7 +4554,7 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t pm_if_node_t *node = PM_ALLOC_NODE(parser, pm_if_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_if_node_t) { { @@ -4585,10 +4585,10 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_statements_node_t *if_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(if_statements, true_expression); + pm_statements_node_body_append(parser, if_statements, true_expression); pm_statements_node_t *else_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(else_statements, false_expression); + pm_statements_node_body_append(parser, else_statements, false_expression); pm_token_t end_keyword = not_provided(parser); pm_else_node_t *else_node = pm_else_node_create(parser, colon, else_statements, &end_keyword); @@ -6609,8 +6609,25 @@ pm_statements_node_body_update(pm_statements_node_t *node, pm_node_t *statement) * Append a new node to the given StatementsNode node's body. */ static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement) { +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement) { pm_statements_node_body_update(node, statement); + + if (node->body.size > 0) { + const pm_node_t *previous = node->body.nodes[node->body.size - 1]; + + switch (PM_NODE_TYPE(previous)) { + case PM_BREAK_NODE: + case PM_NEXT_NODE: + case PM_REDO_NODE: + case PM_RETRY_NODE: + case PM_RETURN_NODE: + pm_parser_warn_node(parser, previous, PM_WARN_UNREACHABLE_STATEMENT); + break; + default: + break; + } + } + pm_node_list_append(&node->body, statement); pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE); } @@ -7173,7 +7190,7 @@ pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_unless_node_t *node = PM_ALLOC_NODE(parser, pm_unless_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_unless_node_t) { { @@ -13007,7 +13024,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop parsing the // statements now. @@ -16762,7 +16779,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // and we didn't return a multiple assignment node, then we can return a // regular parentheses node now. pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } @@ -16772,7 +16789,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // We'll do that here. context_push(parser, PM_CONTEXT_PARENS); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); // If we didn't find a terminator and we didn't find a right // parenthesis, then this is a syntax error. @@ -16783,7 +16800,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // Parse each statement within the parentheses. while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop // parsing the statements now. @@ -17893,7 +17910,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statement = (pm_node_t *) pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); } - pm_statements_node_body_append((pm_statements_node_t *) statements, statement); + pm_statements_node_body_append(parser, (pm_statements_node_t *) statements, statement); pm_do_loop_stack_pop(parser); context_pop(parser); end_keyword = not_provided(parser); @@ -19864,7 +19881,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_UNTIL_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_UNTIL_PREDICATE); return (pm_node_t *) pm_until_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); @@ -19872,7 +19889,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_WHILE_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_WHILE_PREDICATE); return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); @@ -20217,7 +20234,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2)) ); - pm_statements_node_body_append(statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( + pm_statements_node_body_append(parser, statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( parser, arguments, pm_parser_constant_id_constant(parser, "print", 5) @@ -20263,7 +20280,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { } pm_statements_node_t *wrapped_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( + pm_statements_node_body_append(parser, wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( parser, (pm_node_t *) pm_call_node_fcall_synthesized_create(parser, arguments, pm_parser_constant_id_constant(parser, "gets", 4)), statements diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 5044a69c2b05b1..209afd2ee5a025 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -352,6 +352,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_WARN_LITERAL_IN_CONDITION_VERBOSE] = { "%sliteral in %s", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_SHEBANG_CARRIAGE_RETURN] = { "shebang line ending with \\r may cause problems", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_UNEXPECTED_CARRIAGE_RETURN] = { "encountered \\r in middle of line, treated as a mere space", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_UNREACHABLE_STATEMENT] = { "statement not reached", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_VOID_STATEMENT] = { "possibly useless use of %.*s in void context", PM_WARNING_LEVEL_VERBOSE } }; diff --git a/test/prism/warnings_test.rb b/test/prism/warnings_test.rb index ff9c306c9900be..7eb1bbd2e16ec3 100644 --- a/test/prism/warnings_test.rb +++ b/test/prism/warnings_test.rb @@ -187,6 +187,20 @@ def test_void_statements refute_warning("@foo", compare: false, scopes: [[]]) end + def test_unreachable_statement + assert_warning("begin; rescue; retry; foo; end", "statement not reached") + + assert_warning("return; foo", "statement not reached") + + assert_warning("tap { break; foo }", "statement not reached") + assert_warning("tap { break 1; foo }", "statement not reached") + + assert_warning("tap { next; foo }", "statement not reached") + assert_warning("tap { next 1; foo }", "statement not reached") + + assert_warning("tap { redo; foo }", "statement not reached") + end + private def assert_warning(source, message) From 3629d4df66a09334a764f487c431c501a60e18fe Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 10:35:51 -0400 Subject: [PATCH 095/135] [PRISM] Enable passing tests for prism --- spec/prism.mspec | 1 - test/.excludes-prism/TestParse.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/spec/prism.mspec b/spec/prism.mspec index efa0d931230f72..b1802143752e26 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -5,7 +5,6 @@ MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") ## Language -MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding an additional duplicate key afterwards") MSpec.register(:exclude, "Hash literal merges multiple nested '**obj' in Hash literals") diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index 15eced1e020c79..7e35aefbbc7b1a 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -27,6 +27,5 @@ exclude(:test_unexpected_token_after_numeric, "unknown") exclude(:test_unterminated_regexp_error, "unknown") exclude(:test_unused_variable, "missing warning") -exclude(:test_void_expr_stmts_value, "missing warning") exclude(:test_void_value_in_rhs, "unknown") exclude(:test_words, "unknown") From edec690e0331b5aa851b3a7c9872b2bf92f3cb21 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Apr 2024 14:30:30 -0400 Subject: [PATCH 096/135] Refactor how object IDs work for special consts We don't need to treat static symbols in any special way since they can't be confused with other special consts or GC managed objects. --- gc.c | 42 +++++++++++++++++------------------ test/ruby/test_objectspace.rb | 5 ++++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/gc.c b/gc.c index df3a3b8a775ab1..f67a5d8326cd30 100644 --- a/gc.c +++ b/gc.c @@ -3460,8 +3460,8 @@ obj_free(rb_objspace_t *objspace, VALUE obj) } -#define OBJ_ID_INCREMENT (sizeof(RVALUE) / 2) -#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT * 2) +#define OBJ_ID_INCREMENT (sizeof(RVALUE)) +#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT) static int object_id_cmp(st_data_t x, st_data_t y) @@ -4468,25 +4468,28 @@ id2ref(VALUE objid) #define NUM2PTR(x) NUM2ULL(x) #endif rb_objspace_t *objspace = &rb_objspace; - VALUE ptr; - void *p0; objid = rb_to_int(objid); if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) { - ptr = NUM2PTR(objid); - if (ptr == Qtrue) return Qtrue; - if (ptr == Qfalse) return Qfalse; - if (NIL_P(ptr)) return Qnil; - if (FIXNUM_P(ptr)) return ptr; - if (FLONUM_P(ptr)) return ptr; + VALUE ptr = NUM2PTR(objid); + if (SPECIAL_CONST_P(ptr)) { + if (ptr == Qtrue) return Qtrue; + if (ptr == Qfalse) return Qfalse; + if (NIL_P(ptr)) return Qnil; + if (FIXNUM_P(ptr)) return ptr; + if (FLONUM_P(ptr)) return ptr; + + if (SYMBOL_P(ptr)) { + // Check that the symbol is valid + if (rb_static_id_valid_p(SYM2ID(ptr))) { + return ptr; + } + else { + rb_raise(rb_eRangeError, "%p is not symbol id value", (void *)ptr); + } + } - ptr = obj_id_to_ref(objid); - if ((ptr % sizeof(RVALUE)) == (4 << 2)) { - ID symid = ptr / sizeof(RVALUE); - p0 = (void *)ptr; - if (!rb_static_id_valid_p(symid)) - rb_raise(rb_eRangeError, "%p is not symbol id value", p0); - return ID2SYM(symid); + rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not id value", rb_int2str(objid, 10)); } } @@ -4519,10 +4522,7 @@ os_id2ref(VALUE os, VALUE objid) static VALUE rb_find_object_id(VALUE obj, VALUE (*get_heap_object_id)(VALUE)) { - if (STATIC_SYM_P(obj)) { - return (SYM2ID(obj) * sizeof(RVALUE) + (4 << 2)) | FIXNUM_FLAG; - } - else if (FLONUM_P(obj)) { + if (FLONUM_P(obj)) { #if SIZEOF_LONG == SIZEOF_VOIDP return LONG2NUM((SIGNED_VALUE)obj); #else diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index a7cfb064a87e10..1c97bd517e46cd 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -66,8 +66,11 @@ def test_id2ref_invalid_argument end def test_id2ref_invalid_symbol_id + # RB_STATIC_SYM_P checks for static symbols by checking that the bottom + # 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make + # sure that the bottom 8 bits remain unchanged. msg = /is not symbol id value/ - assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]) } + assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + 256) } end def test_count_objects From 9bab179ca3ac9cc13c8940e3d1fa67c881507d68 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 11 Apr 2024 14:35:19 -0400 Subject: [PATCH 097/135] Don't treat flonum specially in object ID flonum is just a special constant, so we don't need to treat it in any special way. --- gc.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gc.c b/gc.c index f67a5d8326cd30..9be07febc8258d 100644 --- a/gc.c +++ b/gc.c @@ -4522,16 +4522,13 @@ os_id2ref(VALUE os, VALUE objid) static VALUE rb_find_object_id(VALUE obj, VALUE (*get_heap_object_id)(VALUE)) { - if (FLONUM_P(obj)) { + if (SPECIAL_CONST_P(obj)) { #if SIZEOF_LONG == SIZEOF_VOIDP return LONG2NUM((SIGNED_VALUE)obj); #else return LL2NUM((SIGNED_VALUE)obj); #endif } - else if (SPECIAL_CONST_P(obj)) { - return LONG2NUM((SIGNED_VALUE)obj); - } return get_heap_object_id(obj); } From 0424c1fa7b2554f4f7768635f6414281f895d3df Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 11:17:14 -0400 Subject: [PATCH 098/135] [ruby/prism] Fix up embdoc lexing on EOF https://github.com/ruby/prism/commit/8ee43be26d --- prism/prism.c | 28 ++++++++++++++++++++-------- prism/templates/src/diagnostic.c.erb | 2 +- test/prism/errors_test.rb | 13 +++++++++---- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index c370a8b2bf672a..9fabc5ec0c5a0e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -9605,15 +9605,23 @@ lex_embdoc(pm_parser_t *parser) { pm_comment_t *comment = parser_comment(parser, PM_COMMENT_EMBDOC); if (comment == NULL) return PM_TOKEN_EOF; - // Now, loop until we find the end of the embedded documentation or the end of - // the file. + // Now, loop until we find the end of the embedded documentation or the end + // of the file. while (parser->current.end + 4 <= parser->end) { parser->current.start = parser->current.end; - // If we've hit the end of the embedded documentation then we'll return that - // token here. - if (memcmp(parser->current.end, "=end", 4) == 0 && - (parser->current.end + 4 == parser->end || pm_char_is_whitespace(parser->current.end[4]))) { + // If we've hit the end of the embedded documentation then we'll return + // that token here. + if ( + (memcmp(parser->current.end, "=end", 4) == 0) && + ( + (parser->current.end + 4 == parser->end) || // end of file + pm_char_is_whitespace(parser->current.end[4]) || // whitespace + (parser->current.end[4] == '\0') || // NUL or end of script + (parser->current.end[4] == '\004') || // ^D + (parser->current.end[4] == '\032') // ^Z + ) + ) { const uint8_t *newline = next_newline(parser->current.end, parser->end - parser->current.end); if (newline == NULL) { @@ -10425,9 +10433,13 @@ parser_lex(pm_parser_t *parser) { // = => =~ == === =begin case '=': - if (current_token_starts_line(parser) && (parser->current.end + 5 <= parser->end) && memcmp(parser->current.end, "begin", 5) == 0 && pm_char_is_whitespace(peek_offset(parser, 5))) { + if ( + current_token_starts_line(parser) && + (parser->current.end + 5 <= parser->end) && + memcmp(parser->current.end, "begin", 5) == 0 && + (pm_char_is_whitespace(peek_offset(parser, 5)) || (peek_offset(parser, 5) == '\0')) + ) { pm_token_type_t type = lex_embdoc(parser); - if (type == PM_TOKEN_EOF) { LEX(type); } diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 209afd2ee5a025..924d9e6b3fb832 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -152,7 +152,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBDOC_TERM] = { "embedded document meets end of file", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index cabc96c8eabdae..0a06e4bd38fb39 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -105,9 +105,14 @@ def test_pre_execution_context end def test_unterminated_embdoc - assert_errors expression("1"), "1\n=begin\n", [ - ["could not find a terminator for the embedded document", 2..9] - ] + message = "embedded document meets end of file" + assert_error_messages "=begin", [message] + assert_error_messages "=begin\n", [message] + + refute_error_messages "=begin\n=end" + refute_error_messages "=begin\n=end\0" + refute_error_messages "=begin\n=end\C-d" + refute_error_messages "=begin\n=end\C-z" end def test_unterminated_i_list @@ -2217,7 +2222,7 @@ def assert_error_messages(source, errors) def refute_error_messages(source) assert_valid_syntax(source) - assert Prism.parse_success?(source) + assert Prism.parse_success?(source), "Expected #{source.inspect} to parse successfully" end def assert_warning_messages(source, warnings) From 52b862398d264e46ad2c04286ef464cc513322ab Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 13:18:47 -0400 Subject: [PATCH 099/135] [ruby/prism] Syntax error for block argument on yield https://github.com/ruby/prism/commit/9feeafbc67 --- prism/config.yml | 1 + prism/prism.c | 10 ++++++++++ prism/templates/src/diagnostic.c.erb | 1 + test/prism/errors_test.rb | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/prism/config.yml b/prism/config.yml index 17fff0431a7cf9..a8b1c84ca54c84 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -235,6 +235,7 @@ errors: - TERNARY_EXPRESSION_TRUE - UNARY_RECEIVER - UNDEF_ARGUMENT + - UNEXPECTED_BLOCK_ARGUMENT - UNEXPECTED_TOKEN_CLOSE_CONTEXT - UNEXPECTED_TOKEN_IGNORE - UNTIL_TERM diff --git a/prism/prism.c b/prism/prism.c index 9fabc5ec0c5a0e..55e703eb466ca9 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -17601,6 +17601,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, false, accepts_command_call); + // It's possible that we've parsed a block argument through our + // call to parse_arguments_list. If we found one, we should mark it + // as invalid and destroy it, as we don't have a place for it on the + // yield node. + if (arguments.block != NULL) { + pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); + pm_node_destroy(parser, arguments.block); + arguments.block = NULL; + } + pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc); if (!parser->parsing_eval) parse_yield(parser, node); diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 924d9e6b3fb832..6275218cb171d3 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -317,6 +317,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_BLOCK_ARGUMENT] = { "block argument should not be given", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 0a06e4bd38fb39..09f37eb5b3c650 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -2202,6 +2202,10 @@ def test_duplicate_pattern_hash_key refute_error_messages "case (); in [{a:1}, {a:2}]; end" end + def test_unexpected_block + assert_error_messages "def foo = yield(&:+)", ["block argument should not be given"] + end + private def assert_errors(expected, source, errors, check_valid_syntax: true) From 1521af3259b12570bb9eb2b2a735641b35e33ec7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 13:43:30 -0400 Subject: [PATCH 100/135] [ruby/prism] Better error message on invalid def https://github.com/ruby/prism/commit/d398e7d22c --- prism/config.yml | 2 +- prism/prism.c | 23 ++++++++++------------- prism/templates/src/diagnostic.c.erb | 4 ++-- prism/templates/src/token_type.c.erb | 4 ++-- test/prism/errors_test.rb | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index a8b1c84ca54c84..a0abb98ef7e3a8 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -60,7 +60,6 @@ errors: - DEF_ENDLESS - DEF_ENDLESS_SETTER - DEF_NAME - - DEF_NAME_AFTER_RECEIVER - DEF_PARAMS_TERM - DEF_PARAMS_TERM_PAREN - DEF_RECEIVER @@ -97,6 +96,7 @@ errors: - EXPECT_EXPRESSION_AFTER_STAR - EXPECT_IDENT_REQ_PARAMETER - EXPECT_LPAREN_REQ_PARAMETER + - EXPECT_MESSAGE - EXPECT_RBRACKET - EXPECT_RPAREN - EXPECT_RPAREN_AFTER_MULTI diff --git a/prism/prism.c b/prism/prism.c index 55e703eb466ca9..640adb2e2c609c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -15281,6 +15281,7 @@ parse_method_definition_name(pm_parser_t *parser) { parser_lex(parser); return parser->previous; default: + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_DEF_NAME, pm_token_type_human(parser->current.type)); return (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->current.start, .end = parser->current.end }; } } @@ -17722,7 +17723,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = NULL; pm_token_t operator = not_provided(parser); - pm_token_t name = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = def_keyword.end, .end = def_keyword.end }; + pm_token_t name; // This context is necessary for lexing `...` in a bare params // correctly. It must be pushed before lexing the first param, so it @@ -17804,7 +17805,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b receiver = (pm_node_t *) pm_true_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD_FALSE: - receiver = (pm_node_t *)pm_false_node_create(parser, &identifier); + receiver = (pm_node_t *) pm_false_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD___FILE__: receiver = (pm_node_t *) pm_source_file_node_create(parser, &identifier); @@ -17826,9 +17827,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b break; } case PM_TOKEN_PARENTHESIS_LEFT: { - // The current context is `PM_CONTEXT_DEF_PARAMS`, however the inner expression - // of this parenthesis should not be processed under this context. - // Thus, the context is popped here. + // The current context is `PM_CONTEXT_DEF_PARAMS`, however + // the inner expression of this parenthesis should not be + // processed under this context. Thus, the context is popped + // here. context_pop(parser); parser_lex(parser); @@ -17845,7 +17847,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b operator = parser->previous; receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, expression, &rparen); - // To push `PM_CONTEXT_DEF_PARAMS` again is for the same reason as described the above. + // To push `PM_CONTEXT_DEF_PARAMS` again is for the same + // reason as described the above. pm_parser_scope_push(parser, true); context_push(parser, PM_CONTEXT_DEF_PARAMS); name = parse_method_definition_name(parser); @@ -17857,12 +17860,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b break; } - // If, after all that, we were unable to find a method name, add an - // error to the error list. - if (name.type == PM_TOKEN_MISSING) { - pm_parser_err_previous(parser, PM_ERR_DEF_NAME); - } - pm_token_t lparen; pm_token_t rparen; pm_parameters_node_t *params; @@ -19856,7 +19853,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t break; } default: { - pm_parser_err_current(parser, PM_ERR_DEF_NAME); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_MESSAGE, pm_token_type_human(parser->current.type)); message = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; } } diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 6275218cb171d3..f56f523ce66e0f 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -144,8 +144,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_SYNTAX }, @@ -181,6 +180,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_SYNTAX }, diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 493d46f1880652..5ea919906259dc 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -326,13 +326,13 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_STAR_STAR_EQUAL: return "'**='"; case PM_TOKEN_STRING_BEGIN: - return "string beginning"; + return "string literal"; case PM_TOKEN_STRING_CONTENT: return "string content"; case PM_TOKEN_STRING_END: return "string ending"; case PM_TOKEN_SYMBOL_BEGIN: - return "symbol beginning"; + return "symbol literal"; case PM_TOKEN_TILDE: return "'~'"; case PM_TOKEN_UAMPERSAND: diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 09f37eb5b3c650..d74c5d54a1e87d 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -341,7 +341,7 @@ def test_aliasing_global_variable_with_global_number_variable def test_def_with_expression_receiver_and_no_identifier assert_errors expression("def (a); end"), "def (a); end", [ ["expected a `.` or `::` after the receiver in a method definition", 7..7], - ["expected a method name", 7..7] + ["unexpected ';'; expected a method name", 7..8] ] end From 650b5e5aa2ece3446633fa2ecfb202334ad655d3 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 13:11:19 -0400 Subject: [PATCH 101/135] [PRISM] Enable more passing tests --- spec/prism.mspec | 2 -- test/.excludes-prism/TestParse.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/spec/prism.mspec b/spec/prism.mspec index b1802143752e26..16508bd547a5b0 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -24,8 +24,6 @@ MSpec.register(:exclude, "A Symbol literal raises an SyntaxError at parse time w ## Core MSpec.register(:exclude, "IO.popen with a leading Array argument accepts a trailing Hash of Process.exec options") MSpec.register(:exclude, "IO.popen with a leading Array argument accepts an IO mode argument following the Array") -MSpec.register(:exclude, "TracePoint#eval_script is the evald source code") -MSpec.register(:exclude, "TracePoint#event returns the type of event") MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, method, path and line for a :return event") MSpec.register(:exclude, "TracePoint.new includes multiple events when multiple event names are passed as params") MSpec.register(:exclude, "TracePoint#path equals \"(eval at __FILE__:__LINE__)\" inside an eval for :end event") diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index 7e35aefbbc7b1a..9153dcb9283d03 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -1,7 +1,5 @@ exclude(:test_dynamic_constant_assignment, "unknown") exclude(:test_else_without_rescue, "unknown") -exclude(:test_embedded_rd_error, "unknown") -exclude(:test_embedded_rd, "unknown") exclude(:test_error_def_in_argument, "unknown") exclude(:test_float, "unknown") exclude(:test_global_variable, "unknown") From 7baecc2e36521dbfacffe25e1522ca2a3d85bac8 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 13:24:10 -0400 Subject: [PATCH 102/135] [PRISM] Emit parse warnings before raising syntax errors --- prism_compile.c | 18 +++++++++--------- test/.excludes-prism/TestSyntax.rb | 1 - 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 2c50c19c1dfb26..26d94e979d2458 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -8672,15 +8672,6 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) pm_scope_node_init(node, scope_node, NULL); scope_node->filepath_encoding = filepath_encoding; - // If there are errors, raise an appropriate error and free the result. - if (parser->error_list.size > 0) { - VALUE error = pm_parse_process_error(result); - - // TODO: We need to set the backtrace. - // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); - return error; - } - // Emit all of the various warnings from the parse. const pm_diagnostic_t *warning; const char *warning_filepath = (const char *) pm_string_source(&parser->filepath); @@ -8696,6 +8687,15 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) } } + // If there are errors, raise an appropriate error and free the result. + if (parser->error_list.size > 0) { + VALUE error = pm_parse_process_error(result); + + // TODO: We need to set the backtrace. + // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); + return error; + } + // Now set up the constant pool and intern all of the various constants into // their corresponding IDs. scope_node->encoding = rb_enc_find(parser->encoding->name); diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb index b32eb1823c17a0..46f84cd163cb3f 100644 --- a/test/.excludes-prism/TestSyntax.rb +++ b/test/.excludes-prism/TestSyntax.rb @@ -22,7 +22,6 @@ exclude(:test_numbered_parameter, "unknown") exclude(:test_optional_self_reference, "unknown") exclude(:test_parenthesised_statement_argument, "unknown") -exclude(:test_range_at_eol, "unknown") exclude(:test_safe_call_in_massign_lhs, "unknown") exclude(:test_syntax_error_at_newline, "unknown") exclude(:test_unassignable, "unknown") From 10d0abb437e933caa3da48dfd87b40153d1a5bd7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 14:01:42 -0400 Subject: [PATCH 103/135] [PRISM] Enable passing tests from latest prism --- test/.excludes-prism/TestParse.rb | 1 - test/.excludes-prism/TestSyntax.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index 9153dcb9283d03..83af593b158bb6 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -7,7 +7,6 @@ exclude(:test_heredoc_unterminated_interpolation, "unknown") exclude(:test_invalid_char, "unknown") exclude(:test_location_of_invalid_token, "unknown") -exclude(:test_no_blockarg, "unknown") exclude(:test_op_asgn1_with_block, "unknown") exclude(:test_parse_string, "unknown") exclude(:test_percent, "unknown") diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb index 46f84cd163cb3f..cd18324f748f26 100644 --- a/test/.excludes-prism/TestSyntax.rb +++ b/test/.excludes-prism/TestSyntax.rb @@ -13,7 +13,6 @@ exclude(:test_heredoc_cr, "unknown") exclude(:test_heredoc_no_terminator, "unknown") exclude(:test_invalid_encoding_symbol, "unknown") -exclude(:test_invalid_literal_message, "unknown") exclude(:test_it, "https://github.com/ruby/prism/issues/2323") exclude(:test_keyword_invalid_name, "unknown") exclude(:test_keyword_self_reference, "unknown") From 3c0756752c73ca08f366c31e197097b557da23d7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 14:11:30 -0400 Subject: [PATCH 104/135] [ruby/prism] Better error message on statement inside argument list https://github.com/ruby/prism/commit/3b1a99526a --- prism/prism.c | 9 +++++++-- prism/templates/src/diagnostic.c.erb | 2 +- prism/templates/src/token_type.c.erb | 2 +- test/prism/errors_test.rb | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 640adb2e2c609c..91d27f566d4e00 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -14335,9 +14335,14 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } else { pm_accepts_block_stack_push(parser, true); parse_arguments(parser, arguments, true, PM_TOKEN_PARENTHESIS_RIGHT); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_ARGUMENT_TERM_PAREN); - pm_accepts_block_stack_pop(parser); + if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_ARGUMENT_TERM_PAREN, pm_token_type_human(parser->current.type)); + parser->previous.start = parser->previous.end; + parser->previous.type = PM_TOKEN_MISSING; + } + + pm_accepts_block_stack_pop(parser); arguments->closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); } } else if (accepts_command_call && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR, PM_TOKEN_UAMPERSAND)) && !match1(parser, PM_TOKEN_BRACE_LEFT)) { diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index f56f523ce66e0f..97ad451890a2a2 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -105,7 +105,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_ARGUMENT_TERM_PAREN] = { "expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_TERM_PAREN] = { "unexpected %s; expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected `{` after a method call without parenthesis", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX }, diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 5ea919906259dc..1aeecd72b24acb 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -208,7 +208,7 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_KEYWORD_RESCUE: return "'rescue'"; case PM_TOKEN_KEYWORD_RESCUE_MODIFIER: - return "'rescue'"; + return "'rescue' modifier"; case PM_TOKEN_KEYWORD_RETRY: return "'retry'"; case PM_TOKEN_KEYWORD_RETURN: diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index d74c5d54a1e87d..679f6ed0a24d87 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -411,7 +411,7 @@ def test_arguments_after_block def test_arguments_binding_power_for_and assert_error_messages "foo(*bar and baz)", [ - "expected a `)` to close the arguments", + "unexpected 'and'; expected a `)` to close the arguments", "unexpected ')', expecting end-of-input", "unexpected ')', ignoring it" ] From 43a6690d2142a2f07a04c0bb75a0834a23c88fec Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Apr 2024 19:47:04 -0400 Subject: [PATCH 105/135] [PRISM] Enable passing test_parenthesised_statement_argument --- test/.excludes-prism/TestSyntax.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb index cd18324f748f26..f8ae776930c90d 100644 --- a/test/.excludes-prism/TestSyntax.rb +++ b/test/.excludes-prism/TestSyntax.rb @@ -20,7 +20,6 @@ exclude(:test_methoddef_endless_command, "unknown") exclude(:test_numbered_parameter, "unknown") exclude(:test_optional_self_reference, "unknown") -exclude(:test_parenthesised_statement_argument, "unknown") exclude(:test_safe_call_in_massign_lhs, "unknown") exclude(:test_syntax_error_at_newline, "unknown") exclude(:test_unassignable, "unknown") From c479492a6701dcef3d3a96de8946ecf7beb079d4 Mon Sep 17 00:00:00 2001 From: Zack Deveau Date: Fri, 19 Jan 2024 15:01:46 -0500 Subject: [PATCH 106/135] Resize ary when `Array#sort!` block modifies embedded ary In cases where `rb_ary_sort_bang` is called with a block and tmp is an embedded array, we need to account for the block potentially impacting the capacity of ary. ex: ``` var_0 = (1..70).to_a var_0.sort! do |var_0_block_129, var_1_block_129| var_0.pop var_1_block_129 <=> var_0_block_129 end.shift(3) ``` The above example can put the array into a corrupted state resulting in a heap buffer overflow and possible segfault: ``` ERROR: AddressSanitizer: heap-buffer-overflow on address [...] WRITE of size 560 at 0x60b0000034f0 thread T0 [...] ``` This commit adds a conditional to determine when the capacity of ary has been modified by the provided block. If this is the case, ensure that the capacity of ary is adjusted to handle at minimum the len of tmp. --- array.c | 3 +++ test/ruby/test_array.rb | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/array.c b/array.c index bcf98fc0125ae1..56f3584e1dbc81 100644 --- a/array.c +++ b/array.c @@ -3393,6 +3393,9 @@ rb_ary_sort_bang(VALUE ary) rb_ary_unshare(ary); FL_SET_EMBED(ary); } + if (ARY_EMBED_LEN(tmp) > ARY_CAPA(ary)) { + ary_resize_capa(ary, ARY_EMBED_LEN(tmp)); + } ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); ARY_SET_LEN(ary, ARY_EMBED_LEN(tmp)); } diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 6e84bdbd02dc25..9560fca9580267 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -3556,6 +3556,15 @@ def test_big_array_literal_with_kwsplat assert_equal(10000, eval(lit).size) end + def test_array_safely_modified_by_sort_block + var_0 = (1..70).to_a + var_0.sort! do |var_0_block_129, var_1_block_129| + var_0.pop + var_1_block_129 <=> var_0_block_129 + end.shift(3) + assert_equal((1..67).to_a.reverse, var_0) + end + private def need_continuation unless respond_to?(:callcc, true) From 5970386a2edb0f8ba4b9b155091b42f225054385 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 13 Apr 2024 13:22:18 +0900 Subject: [PATCH 107/135] Use `rb_parser_string_t *` for `delayed.token` --- parse.y | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/parse.y b/parse.y index 3665f9b7c07fd6..bd355e308a446a 100644 --- a/parse.y +++ b/parse.y @@ -531,7 +531,7 @@ struct parser_params { VALUE debug_output; struct { - VALUE token; + rb_parser_string_t *token; int beg_line; int beg_col; int end_line; @@ -707,11 +707,13 @@ after_pop_stack(int len, struct parser_params *p) #define TOK_INTERN() intern_cstr(tok(p), toklen(p), p->enc) #define VALID_SYMNAME_P(s, l, enc, type) (rb_enc_symname_type(s, l, enc, (1U<<(type))) == (int)(type)) +#ifndef RIPPER static inline bool end_with_newline_p(struct parser_params *p, VALUE str) { return RSTRING_LEN(str) > 0 && RSTRING_END(str)[-1] == '\n'; } +#endif static void pop_pvtbl(struct parser_params *p, st_table *tbl) @@ -2054,6 +2056,7 @@ parser_memhash(const void *ptr, long len) #define PARSER_STRING_PTR(str) (str->ptr) #define PARSER_STRING_LEN(str) (str->len) +#define PARSER_STRING_END(str) (&str->ptr[str->len]) #define STRING_SIZE(str) ((size_t)str->len + 1) #define STRING_TERM_LEN(str) (1) #define STRING_TERM_FILL(str) (str->ptr[str->len] = '\0') @@ -2068,6 +2071,12 @@ parser_memhash(const void *ptr, long len) ((ptrvar) = str->ptr, \ (lenvar) = str->len) +static inline bool +parser_string_end_with_newline_p(struct parser_params *p, rb_parser_string_t *str) +{ + return PARSER_STRING_LEN(str) > 0 && PARSER_STRING_END(str)[-1] == '\n'; +} + static rb_parser_string_t * rb_parser_string_new(rb_parser_t *p, const char *ptr, long len) { @@ -2185,13 +2194,11 @@ PARSER_ENC_CODERANGE_CLEAR(rb_parser_string_t *str) str->coderange = RB_PARSER_ENC_CODERANGE_UNKNOWN; } -#ifndef RIPPER static bool PARSER_ENC_CODERANGE_ASCIIONLY(rb_parser_string_t *str) { return PARSER_ENC_CODERANGE(str) == RB_PARSER_ENC_CODERANGE_7BIT; } -#endif static bool PARSER_ENC_CODERANGE_CLEAN_P(int cr) @@ -2256,7 +2263,6 @@ rb_parser_enc_str_coderange(struct parser_params *p, rb_parser_string_t *str) return cr; } -#ifndef RIPPER static rb_parser_string_t * rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_encoding *enc) { @@ -2269,7 +2275,6 @@ rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_enc rb_parser_string_set_encoding(str, enc); return str; } -#endif static bool rb_parser_is_ascii_string(struct parser_params *p, rb_parser_string_t *str) @@ -2480,6 +2485,13 @@ rb_parser_enc_cr_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, c } +static rb_parser_string_t * +rb_parser_enc_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, const char *ptr, long len, + rb_encoding *ptr_enc) +{ + return rb_parser_enc_cr_str_buf_cat(p, str, ptr, len, ptr_enc, RB_PARSER_ENC_CODERANGE_UNKNOWN, NULL); +} + static rb_parser_string_t * rb_parser_str_buf_append(struct parser_params *p, rb_parser_string_t *str, rb_parser_string_t *str2) { @@ -7030,7 +7042,7 @@ do { \ # define yylval_id() (yylval.id) #define set_yylval_noname() set_yylval_id(keyword_nil) -#define has_delayed_token(p) (!NIL_P(p->delayed.token)) +#define has_delayed_token(p) (p->delayed.token != NULL) #ifndef RIPPER #define literal_flush(p, ptr) ((p)->lex.ptok = (ptr)) @@ -7173,11 +7185,13 @@ parser_dispatch_delayed_token(struct parser_params *p, enum yytokentype t, int l RUBY_SET_YYLLOC_OF_DELAYED_TOKEN(*p->yylloc); if (p->keep_tokens) { - rb_parser_string_t *str = rb_str_to_parser_string(p, p->delayed.token); - parser_append_tokens(p, str, t, line); + /* p->delayed.token is freed by rb_parser_tokens_free */ + parser_append_tokens(p, p->delayed.token, t, line); + } else { + rb_parser_string_free(p, p->delayed.token); } - p->delayed.token = Qnil; + p->delayed.token = NULL; } #else #define literal_flush(p, ptr) ((void)(ptr)) @@ -7214,14 +7228,16 @@ ripper_dispatch_delayed_token(struct parser_params *p, enum yytokentype t) /* save and adjust the location to delayed token for callbacks */ int saved_line = p->ruby_sourceline; const char *saved_tokp = p->lex.ptok; - VALUE s_value; + VALUE s_value, str; if (!has_delayed_token(p)) return; p->ruby_sourceline = p->delayed.beg_line; p->lex.ptok = p->lex.pbeg + p->delayed.beg_col; - s_value = ripper_dispatch1(p, ripper_token2eventid(t), p->delayed.token); + str = rb_str_new_mutable_parser_string(p->delayed.token); + rb_parser_string_free(p, p->delayed.token); + s_value = ripper_dispatch1(p, ripper_token2eventid(t), str); set_parser_s_value(s_value); - p->delayed.token = Qnil; + p->delayed.token = NULL; p->ruby_sourceline = saved_line; p->lex.ptok = saved_tokp; } @@ -7910,7 +7926,7 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int if (tok < end) { if (has_delayed_token(p)) { - bool next_line = end_with_newline_p(p, p->delayed.token); + bool next_line = parser_string_end_with_newline_p(p, p->delayed.token); int end_line = (next_line ? 1 : 0) + p->delayed.end_line; int end_col = (next_line ? 0 : p->delayed.end_col); if (end_line != p->ruby_sourceline || end_col != tok - p->lex.pbeg) { @@ -7918,12 +7934,12 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int } } if (!has_delayed_token(p)) { - p->delayed.token = rb_str_buf_new(end - tok); - rb_enc_associate(p->delayed.token, p->enc); + p->delayed.token = rb_parser_string_new(p, 0, 0); + rb_parser_enc_associate(p, p->delayed.token, p->enc); p->delayed.beg_line = p->ruby_sourceline; p->delayed.beg_col = rb_long2int(tok - p->lex.pbeg); } - rb_str_buf_cat(p->delayed.token, tok, end - tok); + rb_parser_str_buf_cat(p, p->delayed.token, tok, end - tok); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(end - p->lex.pbeg); p->lex.ptok = end; @@ -8792,7 +8808,7 @@ flush_string_content(struct parser_params *p, rb_encoding *enc) if (has_delayed_token(p)) { ptrdiff_t len = p->lex.pcur - p->lex.ptok; if (len > 0) { - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(p->lex.pcur - p->lex.pbeg); } @@ -9355,7 +9371,7 @@ here_document(struct parser_params *p, rb_strterm_heredoc_t *here) enc = rb_ascii8bit_encoding(); } } - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); } dispatch_delayed_token(p, tSTRING_CONTENT); } @@ -15777,7 +15793,7 @@ parser_initialize(struct parser_params *p) p->lex.lpar_beg = -1; /* make lambda_beginning_p() == FALSE at first */ string_buffer_init(p); p->node_id = 0; - p->delayed.token = Qnil; + p->delayed.token = NULL; p->frozen_string_literal = -1; /* not specified */ #ifndef RIPPER p->error_buffer = Qfalse; @@ -15811,7 +15827,6 @@ rb_ruby_parser_mark(void *ptr) rb_gc_mark(p->lex.input); rb_gc_mark(p->ruby_sourcefile_string); rb_gc_mark((VALUE)p->ast); - rb_gc_mark(p->delayed.token); #ifndef RIPPER rb_gc_mark(p->debug_lines); rb_gc_mark(p->error_buffer); From 924b928e35bb54b0ba44028b780481bf979cc6c9 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 13 Apr 2024 23:06:03 +0900 Subject: [PATCH 108/135] Remove unused functions from `struct rb_parser_config_struct` --- ruby_parser.c | 10 ---------- rubyparser.h | 4 ---- universal_parser.c | 5 ----- 3 files changed, 19 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index 40c469f14bb44b..1991735af438c4 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -195,12 +195,6 @@ enc_codelen(int c, void *enc) return rb_enc_codelen(c, (rb_encoding *)enc); } -static VALUE -enc_str_buf_cat(VALUE str, const char *ptr, long len, void *enc) -{ - return rb_enc_str_buf_cat(str, ptr, len, (rb_encoding *)enc); -} - static int enc_mbcput(unsigned int c, void *buf, void *enc) { @@ -460,8 +454,6 @@ static const rb_parser_config_t rb_global_parser_config = { .str_cat_cstr = rb_str_cat_cstr, .str_subseq = rb_str_subseq, .str_new_frozen = rb_str_new_frozen, - .str_buf_new = rb_str_buf_new, - .str_buf_cat = rb_str_buf_cat, .str_modify = rb_str_modify, .str_set_len = rb_str_set_len, .str_cat = rb_str_cat, @@ -471,8 +463,6 @@ static const rb_parser_config_t rb_global_parser_config = { .str_to_interned_str = rb_str_to_interned_str, .is_ascii_string = is_ascii_string2, .enc_str_new = enc_str_new, - .enc_str_buf_cat = enc_str_buf_cat, - .str_buf_append = rb_str_buf_append, .str_vcatf = rb_str_vcatf, .string_value_cstr = rb_string_value_cstr, .rb_sprintf = rb_sprintf, diff --git a/rubyparser.h b/rubyparser.h index 5ba2da8de11f98..c51b9ee44acf78 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -1284,8 +1284,6 @@ typedef struct rb_parser_config_struct { VALUE (*str_cat_cstr)(VALUE str, const char *ptr); VALUE (*str_subseq)(VALUE str, long beg, long len); VALUE (*str_new_frozen)(VALUE orig); - VALUE (*str_buf_new)(long capa); - VALUE (*str_buf_cat)(VALUE, const char*, long); void (*str_modify)(VALUE str); void (*str_set_len)(VALUE str, long len); VALUE (*str_cat)(VALUE str, const char *ptr, long len); @@ -1295,8 +1293,6 @@ typedef struct rb_parser_config_struct { VALUE (*str_to_interned_str)(VALUE); int (*is_ascii_string)(VALUE str); VALUE (*enc_str_new)(const char *ptr, long len, rb_encoding *enc); - VALUE (*enc_str_buf_cat)(VALUE str, const char *ptr, long len, rb_encoding *enc); - VALUE (*str_buf_append)(VALUE str, VALUE str2); RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) VALUE (*str_vcatf)(VALUE str, const char *fmt, va_list ap); char *(*string_value_cstr)(volatile VALUE *ptr); diff --git a/universal_parser.c b/universal_parser.c index d555ea00e88779..23a86311a5e039 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -125,9 +125,6 @@ #define rb_str_cat_cstr p->config->str_cat_cstr #define rb_str_subseq p->config->str_subseq #define rb_str_new_frozen p->config->str_new_frozen -#define rb_str_buf_new p->config->str_buf_new -#undef rb_str_buf_cat -#define rb_str_buf_cat p->config->str_buf_cat #define rb_str_modify p->config->str_modify #define rb_str_set_len p->config->str_set_len #define rb_str_cat p->config->str_cat @@ -139,8 +136,6 @@ #define rb_str_to_interned_str p->config->str_to_interned_str #define is_ascii_string p->config->is_ascii_string #define rb_enc_str_new p->config->enc_str_new -#define rb_enc_str_buf_cat p->config->enc_str_buf_cat -#define rb_str_buf_append p->config->str_buf_append #define rb_str_vcatf p->config->str_vcatf #undef StringValueCStr #define StringValueCStr(v) p->config->string_value_cstr(&(v)) From 38b8bdb8eac2c0dc6a149ef9d13c02ef788ad5ef Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 14 Apr 2024 09:22:10 +0900 Subject: [PATCH 109/135] Remove undefined function's prototype declaration 89cfc152071 removed the definition of these functions. --- ext/ripper/ripper_init.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/ripper/ripper_init.h b/ext/ripper/ripper_init.h index e9e7bc7e5fdbf4..664bb7bce368fc 100644 --- a/ext/ripper/ripper_init.h +++ b/ext/ripper/ripper_init.h @@ -2,8 +2,6 @@ #define RIPPER_INIT_H extern VALUE rb_ripper_none; -VALUE ripper_get_value(VALUE v); -ID ripper_get_id(VALUE v); PRINTF_ARGS(void ripper_compile_error(struct parser_params*, const char *fmt, ...), 2, 3); #endif /* RIPPER_INIT_H */ From 76b10f2ee1bbc962f2b7f178b7dddb30985d56f7 Mon Sep 17 00:00:00 2001 From: "Michael J. Giarlo" Date: Sun, 14 Apr 2024 02:13:06 -0700 Subject: [PATCH 110/135] [ruby/reline] Support `menu-complete-backward` command for upward navigation (https://github.com/ruby/reline/pull/677) Fixes https://github.com/ruby/reline/pull/675 This commit extracts the upward navigation condition in `LineEditor#input_key` to a new private method, and adds a new alias. This change allows Reline to support upward navigation in when a user has configured `inputrc` to map Shift-Tab to `menu-complete-backward`, a common setting in Bash (>= 4.x). Instead of special-casing upward navigation in `LineEditor#input_key`, we now allow it to be processed by the branch that calls `process_key`. The extracted method no longer includes the editing mode check since this check is already made by `#wrap_method_call` by the time `#completion_journey_up` (or `#menu_complete_backward`) is called. Since upward navigation is happening in a method other than `#input_key` now, the `completion_occurs` variable that used to be local to `#input_key` is changed to an instance variable so that the new method can change its value. (I see many examples of mutating such instance variables in `LineEditor`, so I assumed this would be an uncontroversial change consistent with the coding practices already in place.) Test coverage of this change has been added to the emacs and vi `KeyActor` tests. Many thanks to @ima1zumi for their very helpful comments on #675 which encouraged me to contribute this work! https://github.com/ruby/reline/commit/2ccdb374a4 --- lib/reline/line_editor.rb | 28 ++++++++++-------- test/reline/test_key_actor_emacs.rb | 46 +++++++++++++++++++++++++++++ test/reline/test_key_actor_vi.rb | 46 +++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 13 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index c236044e41fed9..f23671a942e405 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -233,6 +233,7 @@ def reset_variables(prompt = '', encoding:) @waiting_operator_vi_arg = nil @completion_journey_state = nil @completion_state = CompletionState::NORMAL + @completion_occurs = false @perfect_matched = nil @menu_info = nil @searching_prompt = nil @@ -1118,42 +1119,35 @@ def input_key(key) end old_lines = @buffer_of_lines.dup @first_char = false - completion_occurs = false + @completion_occurs = false if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord if !@config.disable_completion process_insert(force: true) if @config.autocompletion @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list(:down) + @completion_occurs = move_completed_list(:down) else @completion_journey_state = nil result = call_completion_proc if result.is_a?(Array) - completion_occurs = true + @completion_occurs = true complete(result, false) end end end - elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up - if not @config.disable_completion and @config.autocompletion - process_insert(force: true) - @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list(:up) - end elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) # In vi mode, move completed list even if autocompletion is off if not @config.disable_completion process_insert(force: true) @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) + @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) end elsif Symbol === key.char and respond_to?(key.char, true) process_key(key.char, key.char) else normal_char(key) end - - unless completion_occurs + unless @completion_occurs @completion_state = CompletionState::NORMAL @completion_journey_state = nil end @@ -1164,7 +1158,7 @@ def input_key(key) end modified = old_lines != @buffer_of_lines - if !completion_occurs && modified && !@config.disable_completion && @config.autocompletion + if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion # Auto complete starts only when edited process_insert(force: true) @completion_journey_state = retrieve_completion_journey_state @@ -1433,6 +1427,14 @@ def finish end end + private def completion_journey_up(key) + if not @config.disable_completion and @config.autocompletion + @completion_state = CompletionState::NORMAL + @completion_occurs = move_completed_list(:up) + end + end + alias_method :menu_complete_backward, :completion_journey_up + # Editline:: +ed-unassigned+ This editor command always results in an error. # GNU Readline:: There is no corresponding macro. private def ed_unassigned(key) end # do nothing diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 0dff275f6ec4ee..5d7a01ac2d8fbc 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -789,6 +789,52 @@ def test_completion assert_line_around_cursor('foo_ba', '') end + def test_autocompletion_with_upward_navigation + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:completion_journey_up, :completion_journey_up, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + + def test_autocompletion_with_upward_navigation_and_menu_complete_backward + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:menu_complete_backward, :menu_complete_backward, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + def test_completion_with_indent @line_editor.completion_proc = proc { |word| %w{ diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 91cbd49d74bfd5..838c162d6cb972 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -627,6 +627,52 @@ def test_completion assert_line_around_cursor('foo_bar', '') end + def test_autocompletion_with_upward_navigation + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:completion_journey_up, :completion_journey_up, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + + def test_autocompletion_with_upward_navigation_and_menu_complete_backward + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:menu_complete_backward, :menu_complete_backward, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + def test_completion_with_disable_completion @config.disable_completion = true @line_editor.completion_proc = proc { |word| From 04ba96e619325d6e2c053ae93c2514e9252f0497 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 14 Apr 2024 19:01:38 +0800 Subject: [PATCH 111/135] [ruby/irb] Allow defining custom commands in IRB (https://github.com/ruby/irb/pull/886) This is a feature that has been requested for a long time. It is now possible to define custom commands in IRB. Example usage: ```ruby require "irb/command" class HelloCommand < IRB::Command::Base description "Prints hello world" category "My commands" help_message "It doesn't do more than printing hello world." def execute puts "Hello world" end end IRB::Command.register(:hello, HelloCommand) ``` https://github.com/ruby/irb/commit/888643467c --- lib/irb.rb | 2 +- lib/irb/command.rb | 262 ++---------------------------------- lib/irb/command/edit.rb | 1 + lib/irb/default_commands.rb | 248 ++++++++++++++++++++++++++++++++++ test/irb/test_command.rb | 34 +---- 5 files changed, 268 insertions(+), 279 deletions(-) create mode 100644 lib/irb/default_commands.rb diff --git a/lib/irb.rb b/lib/irb.rb index 723035f15ac4c3..ab50c797c7e9fa 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -10,7 +10,7 @@ require_relative "irb/init" require_relative "irb/context" -require_relative "irb/command" +require_relative "irb/default_commands" require_relative "irb/ruby-lex" require_relative "irb/statement" diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 43cbda36b515d8..19fde56356c47d 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -7,261 +7,23 @@ require_relative "command/base" module IRB # :nodoc: - module Command; end - ExtendCommand = Command + module Command + @commands = {} - # Installs the default irb extensions command bundle. - module ExtendCommandBundle - # See ExtendCommandBundle.execute_as_command?. - NO_OVERRIDE = 0 - OVERRIDE_PRIVATE_ONLY = 0x01 - OVERRIDE_ALL = 0x02 + class << self + attr_reader :commands - @EXTEND_COMMANDS = [ - [ - :irb_context, :Context, "command/context", - [:context, NO_OVERRIDE], - [:conf, NO_OVERRIDE], - ], - [ - :irb_exit, :Exit, "command/exit", - [:exit, OVERRIDE_PRIVATE_ONLY], - [:quit, OVERRIDE_PRIVATE_ONLY], - [:irb_quit, OVERRIDE_PRIVATE_ONLY], - ], - [ - :irb_exit!, :ForceExit, "command/force_exit", - [:exit!, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_current_working_workspace, :CurrentWorkingWorkspace, "command/chws", - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], - [:irb_print_working_workspace, OVERRIDE_ALL], - [:irb_cwws, OVERRIDE_ALL], - [:irb_pwws, OVERRIDE_ALL], - [:irb_current_working_binding, OVERRIDE_ALL], - [:irb_print_working_binding, OVERRIDE_ALL], - [:irb_cwb, OVERRIDE_ALL], - [:irb_pwb, OVERRIDE_ALL], - ], - [ - :irb_change_workspace, :ChangeWorkspace, "command/chws", - [:chws, NO_OVERRIDE], - [:cws, NO_OVERRIDE], - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], - [:irb_change_binding, OVERRIDE_ALL], - [:irb_cb, OVERRIDE_ALL], - [:cb, NO_OVERRIDE], - ], - - [ - :irb_workspaces, :Workspaces, "command/pushws", - [:workspaces, NO_OVERRIDE], - [:irb_bindings, OVERRIDE_ALL], - [:bindings, NO_OVERRIDE], - ], - [ - :irb_push_workspace, :PushWorkspace, "command/pushws", - [:pushws, NO_OVERRIDE], - [:irb_pushws, OVERRIDE_ALL], - [:irb_push_binding, OVERRIDE_ALL], - [:irb_pushb, OVERRIDE_ALL], - [:pushb, NO_OVERRIDE], - ], - [ - :irb_pop_workspace, :PopWorkspace, "command/pushws", - [:popws, NO_OVERRIDE], - [:irb_popws, OVERRIDE_ALL], - [:irb_pop_binding, OVERRIDE_ALL], - [:irb_popb, OVERRIDE_ALL], - [:popb, NO_OVERRIDE], - ], - - [ - :irb_load, :Load, "command/load"], - [ - :irb_require, :Require, "command/load"], - [ - :irb_source, :Source, "command/load", - [:source, NO_OVERRIDE], - ], - - [ - :irb, :IrbCommand, "command/subirb"], - [ - :irb_jobs, :Jobs, "command/subirb", - [:jobs, NO_OVERRIDE], - ], - [ - :irb_fg, :Foreground, "command/subirb", - [:fg, NO_OVERRIDE], - ], - [ - :irb_kill, :Kill, "command/subirb", - [:kill, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_debug, :Debug, "command/debug", - [:debug, NO_OVERRIDE], - ], - [ - :irb_edit, :Edit, "command/edit", - [:edit, NO_OVERRIDE], - ], - [ - :irb_break, :Break, "command/break", - ], - [ - :irb_catch, :Catch, "command/catch", - ], - [ - :irb_next, :Next, "command/next" - ], - [ - :irb_delete, :Delete, "command/delete", - [:delete, NO_OVERRIDE], - ], - [ - :irb_step, :Step, "command/step", - [:step, NO_OVERRIDE], - ], - [ - :irb_continue, :Continue, "command/continue", - [:continue, NO_OVERRIDE], - ], - [ - :irb_finish, :Finish, "command/finish", - [:finish, NO_OVERRIDE], - ], - [ - :irb_backtrace, :Backtrace, "command/backtrace", - [:backtrace, NO_OVERRIDE], - [:bt, NO_OVERRIDE], - ], - [ - :irb_debug_info, :Info, "command/info", - [:info, NO_OVERRIDE], - ], - - [ - :irb_help, :Help, "command/help", - [:help, NO_OVERRIDE], - [:show_cmds, NO_OVERRIDE], - ], - - [ - :irb_show_doc, :ShowDoc, "command/show_doc", - [:show_doc, NO_OVERRIDE], - ], - - [ - :irb_info, :IrbInfo, "command/irb_info" - ], - - [ - :irb_ls, :Ls, "command/ls", - [:ls, NO_OVERRIDE], - ], - - [ - :irb_measure, :Measure, "command/measure", - [:measure, NO_OVERRIDE], - ], - - [ - :irb_show_source, :ShowSource, "command/show_source", - [:show_source, NO_OVERRIDE], - ], - [ - :irb_whereami, :Whereami, "command/whereami", - [:whereami, NO_OVERRIDE], - ], - [ - :irb_history, :History, "command/history", - [:history, NO_OVERRIDE], - [:hist, NO_OVERRIDE], - ], - - [ - :irb_disable_irb, :DisableIrb, "command/disable_irb", - [:disable_irb, NO_OVERRIDE], - ], - ] - - def self.command_override_policies - @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| - [[cmd_name, OVERRIDE_ALL]] + aliases - end.to_h - end - - def self.execute_as_command?(name, public_method:, private_method:) - case command_override_policies[name] - when OVERRIDE_ALL - true - when OVERRIDE_PRIVATE_ONLY - !public_method - when NO_OVERRIDE - !public_method && !private_method - end - end - - def self.command_names - command_override_policies.keys.map(&:to_s) - end - - @@commands = [] - - def self.all_commands_info - return @@commands unless @@commands.empty? - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name - end - - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - - klass = Command.const_get(cmd_class, false) - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[cmd_name] - aliases += additional_aliases - end - - display_name = aliases.shift || cmd_name - @@commands << { display_name: display_name, description: klass.description, category: klass.category } + # Registers a command with the given name. + # Aliasing is intentionally not supported at the moment. + def register(name, command_class) + @commands[name] = [command_class, []] end - @@commands - end - - # Convert a command name to its implementation class if such command exists - def self.load_command(command) - command = command.to_sym - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } - - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - return Command.const_get(cmd_class, false) + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name] = [command_class, aliases] end - nil - end - - def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } - @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - - # Just clear memoized values - @@commands = [] - @@command_override_policies = nil end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index 480100bfc7c2df..3c4a54e5e2fa64 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -1,5 +1,6 @@ require 'shellwords' +require_relative "../color" require_relative "../source_finder" module IRB diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb new file mode 100644 index 00000000000000..6025b0547da0be --- /dev/null +++ b/lib/irb/default_commands.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require_relative "command" +require_relative "command/context" +require_relative "command/exit" +require_relative "command/force_exit" +require_relative "command/chws" +require_relative "command/pushws" +require_relative "command/subirb" +require_relative "command/load" +require_relative "command/debug" +require_relative "command/edit" +require_relative "command/break" +require_relative "command/catch" +require_relative "command/next" +require_relative "command/delete" +require_relative "command/step" +require_relative "command/continue" +require_relative "command/finish" +require_relative "command/backtrace" +require_relative "command/info" +require_relative "command/help" +require_relative "command/show_doc" +require_relative "command/irb_info" +require_relative "command/ls" +require_relative "command/measure" +require_relative "command/show_source" +require_relative "command/whereami" +require_relative "command/history" + +module IRB + ExtendCommand = Command + + # Installs the default irb extensions command bundle. + module ExtendCommandBundle + # See #install_alias_method. + NO_OVERRIDE = 0 + # See #install_alias_method. + OVERRIDE_PRIVATE_ONLY = 0x01 + # See #install_alias_method. + OVERRIDE_ALL = 0x02 + + Command._register_with_aliases(:irb_context, Command::Context, + [ + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], + ) + + Command._register_with_aliases(:irb_exit, Command::Exit, + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_exit!, Command::ForceExit, + [:exit!, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], + [:irb_print_working_workspace, OVERRIDE_ALL], + [:irb_cwws, OVERRIDE_ALL], + [:irb_pwws, OVERRIDE_ALL], + [:irb_current_working_binding, OVERRIDE_ALL], + [:irb_print_working_binding, OVERRIDE_ALL], + [:irb_cwb, OVERRIDE_ALL], + [:irb_pwb, OVERRIDE_ALL], + ) + + Command._register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + [:chws, NO_OVERRIDE], + [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], + [:irb_change_binding, OVERRIDE_ALL], + [:irb_cb, OVERRIDE_ALL], + [:cb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_workspaces, Command::Workspaces, + [:workspaces, NO_OVERRIDE], + [:irb_bindings, OVERRIDE_ALL], + [:bindings, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], + [:irb_push_binding, OVERRIDE_ALL], + [:irb_pushb, OVERRIDE_ALL], + [:pushb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], + [:irb_pop_binding, OVERRIDE_ALL], + [:irb_popb, OVERRIDE_ALL], + [:popb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_load, Command::Load) + Command._register_with_aliases(:irb_require, Command::Require) + Command._register_with_aliases(:irb_source, Command::Source, + [:source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb, Command::IrbCommand) + Command._register_with_aliases(:irb_jobs, Command::Jobs, + [:jobs, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_fg, Command::Foreground, + [:fg, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_kill, Command::Kill, + [:kill, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_debug, Command::Debug, + [:debug, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_edit, Command::Edit, + [:edit, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_break, Command::Break) + Command._register_with_aliases(:irb_catch, Command::Catch) + Command._register_with_aliases(:irb_next, Command::Next) + Command._register_with_aliases(:irb_delete, Command::Delete, + [:delete, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_step, Command::Step, + [:step, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_continue, Command::Continue, + [:continue, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_finish, Command::Finish, + [:finish, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_backtrace, Command::Backtrace, + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_debug_info, Command::Info, + [:info, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_help, Command::Help, + [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_doc, Command::ShowDoc, + [:show_doc, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_info, Command::IrbInfo) + + Command._register_with_aliases(:irb_ls, Command::Ls, + [:ls, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_measure, Command::Measure, + [:measure, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_source, Command::ShowSource, + [:show_source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_whereami, Command::Whereami, + [:whereami, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_history, Command::History, + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE] + ) + + def self.all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + Command.commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def self.command_override_policies + @@command_override_policies ||= Command.commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def self.load_command(command) + command = command.to_sym + Command.commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + + # Deprecated. Doesn't have any effect. + @EXTEND_COMMANDS = [] + + # Drepcated. Use Command.regiser instead. + def self.def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + @@command_override_policies = nil + end + end +end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index ca90ec92f854e4..03fdd378556bbf 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -212,20 +212,13 @@ def test_irb_info_lang class CustomCommandTestCase < CommandTestCase def setup - super - execute_lines("help\n") # To ensure command initialization is done - @EXTEND_COMMANDS_backup = IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).dup - @cvars_backup = IRB::ExtendCommandBundle.class_variables.to_h do |cvar| - [cvar, IRB::ExtendCommandBundle.class_variable_get(cvar)] - end + @commands_backup = IRB::Command.commands + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) end def teardown - super - IRB::ExtendCommandBundle.instance_variable_set(:@EXTEND_COMMANDS, @EXTEND_COMMANDS_backup) - @cvars_backup.each do |cvar, value| - IRB::ExtendCommandBundle.class_variable_set(cvar, value) - end + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + IRB::Command.instance_variable_set(:@commands, @commands_backup) end end @@ -239,8 +232,7 @@ def execute(arg) end def test_arg - IRB::Command.const_set :PrintArgCommand, PrintArgCommand - IRB::ExtendCommandBundle.def_extend_command(:print_arg, :PrintArgCommand, nil, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) out, err = execute_lines("print_arg\n") assert_empty err assert_include(out, 'arg=""') @@ -260,8 +252,6 @@ def test_arg out, err = execute_lines("pa a r g \n") assert_empty err assert_include(out, 'arg="a r g"') - ensure - IRB::Command.send(:remove_const, :PrintArgCommand) end end @@ -274,20 +264,8 @@ def execute(_arg) end end - def setup - super - IRB::Command.const_set :FooBarCommand, FooBarCommand - end - - def teardown - super - IRB::Command.send(:remove_const, :FooBarCommand) - end - def test_def_extend_command - command = [:foobar, :FooBarCommand, nil, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]] - IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).push(command) - IRB::ExtendCommandBundle.def_extend_command(*command) + IRB::Command._register_with_aliases(:foobar, FooBarCommand, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]) out, err = execute_lines("foobar\n") assert_empty err assert_include(out, "FooBar executed") From 3368913be3838d152e42bde02a94219102b61f71 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Apr 2024 19:43:21 +0900 Subject: [PATCH 112/135] [pty] Fix `ptsname_r` fallback If `posix_openpt` is available, also `ptsname` should be available. --- ext/pty/extconf.rb | 8 +++++--- ext/pty/pty.c | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index 8cf9e30e69786a..1b0958b484a367 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -13,11 +13,13 @@ have_header("util.h") # OpenBSD openpty util = have_library("util", "openpty") end - if have_func("posix_openpt") or + openpt = have_func("posix_openpt") + if openpt + have_func("ptsname_r") or have_func("ptsname") + end + if openpt (util or have_func("openpty")) or have_func("_getpty") or - have_func("ptsname_r") or - have_func("ptsname") or have_func("ioctl") create_makefile('pty') end diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 5e66abcbe217d4..5ca55e41d67744 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -256,7 +256,7 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, RB_GC_GUARD(carg.execarg_obj); } -#if defined(HAVE_PTSNAME) && !defined(HAVE_PTSNAME_R) +#if (defined(HAVE_POSIX_OPENPT) || defined(HAVE_PTSNAME)) && !defined(HAVE_PTSNAME_R) /* glibc only, not obsolete interface on Tru64 or HP-UX */ static int ptsname_r(int fd, char *buf, size_t buflen) From 8d5d6ec6e7dd777a058fbd9b08372a4fba8737b9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 14 Apr 2024 20:35:34 +0900 Subject: [PATCH 113/135] [pty] Fix missing `or` --- ext/pty/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index 1b0958b484a367..da3655cf4d7118 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -17,7 +17,7 @@ if openpt have_func("ptsname_r") or have_func("ptsname") end - if openpt + if openpt or (util or have_func("openpty")) or have_func("_getpty") or have_func("ioctl") From 1648c4436e67ea9653c247087bf349bd431f5146 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 14 Apr 2024 23:28:10 +0900 Subject: [PATCH 114/135] [ruby/reline] Refactor waiting_proc and waiting_operator_proc (https://github.com/ruby/reline/pull/649) * Fix waiting_proc precedence * Fix waiting_operator bugs * Add waiting_proc and vi_waiting_operator test * Fix vi waiting operator arg number vi_arg and vi_waiting_operator_arg should be multiplied * Implement `yy` copies whole line in vi_command mode * Simplify incremental search cancel test * Add complex vi test with waiting_proc and vi_waiting_operator, split test input https://github.com/ruby/reline/commit/777dffae1c --- lib/reline/line_editor.rb | 151 ++++++++++++++++------------ test/reline/test_key_actor_emacs.rb | 8 ++ test/reline/test_key_actor_vi.rb | 63 ++++++++++-- 3 files changed, 149 insertions(+), 73 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index f23671a942e405..62dc753dbafdba 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -229,8 +229,8 @@ def reset_variables(prompt = '', encoding:) @vi_clipboard = '' @vi_arg = nil @waiting_proc = nil - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil @completion_journey_state = nil @completion_state = CompletionState::NORMAL @completion_occurs = false @@ -936,37 +936,23 @@ def dialog_proc_scope_completion_journey_data end private def run_for_operators(key, method_symbol, &block) - if @waiting_operator_proc + if @vi_waiting_operator if VI_MOTIONS.include?(method_symbol) old_byte_pointer = @byte_pointer - @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1 + @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg block.(true) unless @waiting_proc byte_pointer_diff = @byte_pointer - old_byte_pointer @byte_pointer = old_byte_pointer - @waiting_operator_proc.(byte_pointer_diff) - else - old_waiting_proc = @waiting_proc - old_waiting_operator_proc = @waiting_operator_proc - current_waiting_operator_proc = @waiting_operator_proc - @waiting_proc = proc { |k| - old_byte_pointer = @byte_pointer - old_waiting_proc.(k) - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - current_waiting_operator_proc.(byte_pointer_diff) - @waiting_operator_proc = old_waiting_operator_proc - } + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting end else # Ignores operator when not motion is given. block.(false) + cleanup_waiting end - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil - if @vi_arg - @vi_arg = nil - end + @vi_arg = nil else block.(false) end @@ -983,7 +969,7 @@ def dialog_proc_scope_completion_journey_data end def wrap_method_call(method_symbol, method_obj, key, with_operator = false) - if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil? + if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? not_insertion = method_symbol != :ed_insert process_insert(force: not_insertion) end @@ -1002,11 +988,32 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end end + private def cleanup_waiting + @waiting_proc = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + @searching_prompt = nil + @drop_terminate_spaces = false + end + private def process_key(key, method_symbol) + if key.is_a?(Symbol) + cleanup_waiting + elsif @waiting_proc + old_byte_pointer = @byte_pointer + @waiting_proc.call(key) + if @vi_waiting_operator + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting + end + @kill_ring.process + return + end + if method_symbol and respond_to?(method_symbol, true) method_obj = method(method_symbol) - else - method_obj = nil end if method_symbol and key.is_a?(Symbol) if @vi_arg and argumentable?(method_obj) @@ -1028,8 +1035,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end - elsif @waiting_proc - @waiting_proc.(key) elsif method_obj wrap_method_call(method_symbol, method_obj, key) else @@ -1040,9 +1045,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) @vi_arg = nil end end - elsif @waiting_proc - @waiting_proc.(key) - @kill_ring.process elsif method_obj if method_symbol == :ed_argument_digit wrap_method_call(method_symbol, method_obj, key) @@ -2325,46 +2327,63 @@ def finish @byte_pointer = 0 end - private def vi_change_meta(key, arg: 1) - @drop_terminate_spaces = true - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - set_current_line(line) - copy_for_vi(cut) - @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 - @config.editing_mode = :vi_insert - @drop_terminate_spaces = false - } - @waiting_operator_vi_arg = arg + private def vi_change_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @drop_terminate_spaces = true + @vi_waiting_operator = :vi_change_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end end - private def vi_delete_meta(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) - } - @waiting_operator_vi_arg = arg + private def vi_change_meta_confirm(byte_pointer_diff) + vi_delete_meta_confirm(byte_pointer_diff) + @config.editing_mode = :vi_insert + @drop_terminate_spaces = false end - private def vi_yank(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - } - @waiting_operator_vi_arg = arg + private def vi_delete_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_delete_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_delete_meta_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) + set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) + end + + private def vi_yank(key, arg: nil) + if @vi_waiting_operator + copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_yank_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_yank_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) end private def vi_list_or_eof(key) diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 5d7a01ac2d8fbc..8f5676a1d4f170 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1309,6 +1309,14 @@ def test_ed_search_next_history_with_empty assert_line_around_cursor('', '') end + def test_incremental_search_history_cancel_by_symbol_key + # ed_prev_char should move cursor left and cancel incremental search + input_keys("abc\C-r") + input_key_by_symbol(:ed_prev_char) + input_keys('d') + assert_line_around_cursor('abd', 'c') + end + # Unicode emoji test def test_ed_insert_for_include_zwj_emoji omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 838c162d6cb972..e72eedb904d6d5 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -739,10 +739,16 @@ def test_vi_delete_meta_with_vi_next_char end def test_vi_delete_meta_with_arg - input_keys("aaa bbb ccc\C-[02w") - assert_line_around_cursor('aaa bbb ', 'ccc') + input_keys("aaa bbb ccc ddd\C-[03w") + assert_line_around_cursor('aaa bbb ccc ', 'ddd') input_keys('2dl') - assert_line_around_cursor('aaa bbb ', 'c') + assert_line_around_cursor('aaa bbb ccc ', 'd') + input_keys('d2h') + assert_line_around_cursor('aaa bbb cc', 'd') + input_keys('2d3h') + assert_line_around_cursor('aaa ', 'd') + input_keys('dd') + assert_line_around_cursor('', '') end def test_vi_change_meta @@ -765,6 +771,45 @@ def test_vi_change_meta_with_vi_next_word assert_line_around_cursor('foo hog', 'e baz') end + def test_vi_waiting_operator_with_waiting_proc + input_keys("foo foo foo foo foo\C-[0") + input_keys('2d3fo') + assert_line_around_cursor('', ' foo foo') + input_keys('fo') + assert_line_around_cursor(' f', 'oo foo') + end + + def test_vi_waiting_operator_cancel + input_keys("aaa bbb ccc\C-[02w") + assert_line_around_cursor('aaa bbb ', 'ccc') + # dc dy should cancel delete_meta + input_keys('dch') + input_keys('dyh') + # cd cy should cancel change_meta + input_keys('cdh') + input_keys('cyh') + # yd yc should cancel yank_meta + # P should not paste yanked text because yank_meta is canceled + input_keys('ydhP') + input_keys('ychP') + assert_line_around_cursor('aa', 'a bbb ccc') + end + + def test_cancel_waiting_with_symbol_key + input_keys("aaa bbb lll\C-[0") + assert_line_around_cursor('', 'aaa bbb lll') + # ed_next_char should move cursor right and cancel vi_next_char + input_keys('f') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aa', 'a bbb lll') + # ed_next_char should move cursor right and cancel delete_meta + input_keys('d') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aaa ', 'bbb lll') + end + def test_unimplemented_vi_command_should_be_no_op input_keys("abc\C-[h") assert_line_around_cursor('a', 'bc') @@ -773,12 +818,16 @@ def test_unimplemented_vi_command_should_be_no_op end def test_vi_yank - input_keys("foo bar\C-[0") - assert_line_around_cursor('', 'foo bar') + input_keys("foo bar\C-[2h") + assert_line_around_cursor('foo ', 'bar') input_keys('y3l') - assert_line_around_cursor('', 'foo bar') + assert_line_around_cursor('foo ', 'bar') input_keys('P') - assert_line_around_cursor('fo', 'ofoo bar') + assert_line_around_cursor('foo ba', 'rbar') + input_keys('3h3yhP') + assert_line_around_cursor('foofo', 'o barbar') + input_keys('yyP') + assert_line_around_cursor('foofofoofoo barba', 'ro barbar') end def test_vi_end_word_with_operator From 339128b190959ce15de2b3cecbf9c5ac3c402a1b Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Mon, 15 Apr 2024 02:33:48 +0900 Subject: [PATCH 115/135] [ruby/reline] Refactored Default Key Bindings (https://github.com/ruby/reline/pull/678) * Reduce duplicate method * Configured default key mapping with Readline when the method exists * Remove undefined methods https://github.com/ruby/reline/commit/155f7047bb --- lib/reline/key_actor/emacs.rb | 20 ++++++------- lib/reline/key_actor/vi_command.rb | 46 +++++++++++++++--------------- lib/reline/key_actor/vi_insert.rb | 4 +-- lib/reline/line_editor.rb | 7 +---- 4 files changed, 36 insertions(+), 41 deletions(-) diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index a561feee578cd2..4c7b8421afb50a 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -49,7 +49,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 23 ^W :em_kill_region, # 24 ^X - :ed_sequence_lead_in, + :ed_unassigned, # 25 ^Y :em_yank, # 26 ^Z @@ -319,9 +319,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 158 M-^^ :ed_unassigned, # 159 M-^_ - :em_copy_prev_word, - # 160 M-SPACE :ed_unassigned, + # 160 M-SPACE + :em_set_mark, # 161 M-! :ed_unassigned, # 162 M-" @@ -415,7 +415,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 206 M-N :vi_search_next, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :vi_search_prev, # 209 M-Q @@ -431,15 +431,15 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 214 M-V :ed_unassigned, # 215 M-W - :em_copy_region, + :ed_unassigned, # 216 M-X - :ed_command, - # 217 M-Y :ed_unassigned, + # 217 M-Y + :em_yank_pop, # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] @@ -495,9 +495,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 246 M-v :ed_unassigned, # 247 M-w - :em_copy_region, + :ed_unassigned, # 248 M-x - :ed_command, + :ed_unassigned, # 249 M-y :ed_unassigned, # 250 M-z diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb index 98146d2f7794e8..06bb0ba8e44fdb 100644 --- a/lib/reline/key_actor/vi_command.rb +++ b/lib/reline/key_actor/vi_command.rb @@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 7 ^G :ed_unassigned, # 8 ^H - :ed_unassigned, + :ed_prev_char, # 9 ^I :ed_unassigned, # 10 ^J @@ -41,7 +41,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 19 ^S :ed_ignore, # 20 ^T - :ed_unassigned, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 24 ^X :ed_unassigned, # 25 ^Y - :ed_unassigned, + :em_yank, # 26 ^Z :ed_unassigned, # 27 ^[ @@ -75,7 +75,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 36 $ :ed_move_to_end, # 37 % - :vi_match, + :ed_unassigned, # 38 & :ed_unassigned, # 39 ' @@ -89,11 +89,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 43 + :ed_next_history, # 44 , - :vi_repeat_prev_char, + :ed_unassigned, # 45 - :ed_prev_history, # 46 . - :vi_redo, + :ed_unassigned, # 47 / :vi_search_prev, # 48 0 @@ -117,9 +117,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 57 9 :ed_argument_digit, # 58 : - :ed_command, + :ed_unassigned, # 59 ; - :vi_repeat_next_char, + :ed_unassigned, # 60 < :ed_unassigned, # 61 = @@ -157,21 +157,21 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 77 M :ed_unassigned, # 78 N - :vi_repeat_search_prev, + :ed_unassigned, # 79 O - :ed_sequence_lead_in, + :ed_unassigned, # 80 P :vi_paste_prev, # 81 Q :ed_unassigned, # 82 R - :vi_replace_mode, + :ed_unassigned, # 83 S - :vi_substitute_line, + :ed_unassigned, # 84 T :vi_to_prev_char, # 85 U - :vi_undo_line, + :ed_unassigned, # 86 V :ed_unassigned, # 87 W @@ -179,11 +179,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 88 X :ed_delete_prev_char, # 89 Y - :vi_yank_end, + :ed_unassigned, # 90 Z :ed_unassigned, # 91 [ - :ed_sequence_lead_in, + :ed_unassigned, # 92 \ :ed_unassigned, # 93 ] @@ -191,7 +191,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 94 ^ :vi_first_print, # 95 _ - :vi_history_word, + :ed_unassigned, # 96 ` :ed_unassigned, # 97 a @@ -221,7 +221,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 109 m :ed_unassigned, # 110 n - :vi_repeat_search_next, + :ed_unassigned, # 111 o :ed_unassigned, # 112 p @@ -231,11 +231,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 114 r :vi_replace_char, # 115 s - :vi_substitute_char, + :ed_unassigned, # 116 t :vi_to_next_char, # 117 u - :vi_undo, + :ed_unassigned, # 118 v :vi_histedit, # 119 w @@ -253,9 +253,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 125 } :ed_unassigned, # 126 ~ - :vi_change_case, - # 127 ^? :ed_unassigned, + # 127 ^? + :em_delete_prev_char, # 128 M-^@ :ed_unassigned, # 129 M-^A @@ -415,7 +415,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 206 M-N :ed_unassigned, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :ed_unassigned, # 209 M-Q @@ -439,7 +439,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb index b8e89f81d8075d..c3d7f9c12d04de 100644 --- a/lib/reline/key_actor/vi_insert.rb +++ b/lib/reline/key_actor/vi_insert.rb @@ -41,7 +41,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 19 ^S :vi_search_next, # 20 ^T - :ed_insert, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 24 ^X :ed_insert, # 25 ^Y - :ed_insert, + :em_yank, # 26 ^Z :ed_insert, # 27 ^[ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 62dc753dbafdba..38df6bd21131e2 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -33,8 +33,6 @@ class Reline::LineEditor vi_next_big_word vi_prev_big_word vi_end_big_word - vi_repeat_next_char - vi_repeat_prev_char } module CompletionState @@ -1538,6 +1536,7 @@ def finish @byte_pointer = 0 end alias_method :beginning_of_line, :ed_move_to_beg + alias_method :vi_zero, :ed_move_to_beg private def ed_move_to_end(key) @byte_pointer = 0 @@ -2323,10 +2322,6 @@ def finish copy_for_vi(deleted) end - private def vi_zero(key) - @byte_pointer = 0 - end - private def vi_change_meta(key, arg: nil) if @vi_waiting_operator set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? From 515e52a0b1ce61ccaffe9183bcb78dda95a64907 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 7 Apr 2024 10:59:51 +0900 Subject: [PATCH 116/135] Emit `warn` event for duplicated hash keys on ripper Need to use `rb_warn` macro instead of calling `rb_compile_warn` directly to emit `warn` event on ripper. --- internal/parse.h | 1 - parse.y | 14 ++++++-------- test/ripper/test_parser_events.rb | 6 ++++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/internal/parse.h b/internal/parse.h index 8e82c0f8974a00..20367730d1b99c 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -65,7 +65,6 @@ int rb_ruby_parser_end_seen_p(rb_parser_t *p); int rb_ruby_parser_set_yydebug(rb_parser_t *p, int flag); rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); -void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); diff --git a/parse.y b/parse.y index bd355e308a446a..1a052d475742f4 100644 --- a/parse.y +++ b/parse.y @@ -14890,7 +14890,6 @@ nd_type_st_key_enable_p(NODE *node) } } -#ifndef RIPPER static VALUE nd_value(struct parser_params *p, NODE *node) { @@ -14921,8 +14920,8 @@ nd_value(struct parser_params *p, NODE *node) } } -void -rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) +static void +warn_duplicate_keys(struct parser_params *p, NODE *hash) { /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ st_table *literal_keys = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2); @@ -14942,9 +14941,9 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) key = (st_data_t)head; if (st_delete(literal_keys, &key, &data)) { - rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data), - "key %+"PRIsVALUE" is duplicated and overwritten on line %d", - nd_value(p, head), nd_line(head)); + rb_warn2L(nd_line((NODE *)data), + "key %+"PRIsWARN" is duplicated and overwritten on line %d", + nd_value(p, head), WARN_I(nd_line(head))); } st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); } @@ -14952,12 +14951,11 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) } st_free_table(literal_keys); } -#endif static NODE * new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc) { - if (hash) rb_parser_warn_duplicate_keys(p, hash); + if (hash) warn_duplicate_keys(p, hash); return NEW_HASH(hash, loc); } diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 0bad60b2596033..cbae6e7ed59b0a 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -1686,6 +1686,12 @@ def test_warning_duplicated_when_clause assert_equal([3], args) end + def test_warn_duplicated_hash_keys + fmt, *args = warn("{ a: 1, a: 2 }") + assert_match(/is duplicated and overwritten on line/, fmt) + assert_equal([:a, 1], args) + end + def test_warn_cr_in_middle fmt = nil assert_warn("") {fmt, = warn("\r;")} From 9180e33ca3a5886fec3f9e0a2f48072b55914e65 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 27 Mar 2024 07:29:38 +0900 Subject: [PATCH 117/135] show warning for unused block With verbopse mode (-w), the interpreter shows a warning if a block is passed to a method which does not use the given block. Warning on: * the invoked method is written in C * the invoked method is not `initialize` * not invoked with `super` * the first time on the call-site with the invoked method (`obj.foo{}` will be warned once if `foo` is same method) [Feature #15554] `Primitive.attr! :use_block` is introduced to declare that primitive functions (written in C) will use passed block. For minitest, test needs some tweak, so use https://github.com/minitest/minitest/commit/ea9caafc0754b1d6236a490d59e624b53209734a for `test-bundled-gems`. --- NEWS.md | 7 ++++ array.rb | 2 + compile.c | 14 ++++++- dir.rb | 1 + gems/bundled_gems | 2 +- iseq.c | 6 +++ lib/optparse.rb | 4 +- lib/rdoc/context.rb | 2 +- pack.rb | 1 + rjit_c.rb | 1 + test/ruby/test_method.rb | 59 +++++++++++++++++++++++++++++ test/ruby/test_optimization.rb | 2 +- tool/mk_builtin_loader.rb | 2 +- tool/test/testunit/test_parallel.rb | 2 +- trace_point.rb | 5 +++ vm_backtrace.c | 10 ++--- vm_core.h | 1 + vm_insnhelper.c | 57 ++++++++++++++++++++++++++++ 18 files changed, 165 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index bdb4fa1eb1397b..ff08c4e56604ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -111,7 +111,14 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log ## JIT +## Miscellaneous changes + +* Passing a block to a method which doesn't use the passed block will show + a warning on verbose mode (`-w`). + [[Feature #15554]] + [Feature #13557]: https://bugs.ruby-lang.org/issues/13557 +[Feature #15554]: https://bugs.ruby-lang.org/issues/15554 [Feature #16495]: https://bugs.ruby-lang.org/issues/16495 [Feature #18290]: https://bugs.ruby-lang.org/issues/18290 [Feature #18980]: https://bugs.ruby-lang.org/issues/18980 diff --git a/array.rb b/array.rb index 8e809b35c9a58f..f63ff000568186 100644 --- a/array.rb +++ b/array.rb @@ -43,6 +43,8 @@ class Array # Related: #each_index, #reverse_each. def each Primitive.attr! :inline_block + Primitive.attr! :use_block + unless defined?(yield) return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' end diff --git a/compile.c b/compile.c index 3fb1566ad47d90..2a7bdc2c99a941 100644 --- a/compile.c +++ b/compile.c @@ -2098,6 +2098,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons if (block_id) { body->param.block_start = arg_size++; body->param.flags.has_block = TRUE; + body->param.flags.use_block = 1; } iseq_calc_param_size(iseq); @@ -5918,6 +5919,7 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, ADD_INSN(ret, line_node, putnil); ADD_INSN3(ret, line_node, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; return; case NODE_BACK_REF: @@ -8628,6 +8630,9 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node) else if (strcmp(RSTRING_PTR(string), "inline_block") == 0) { ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_INLINE_BLOCK; } + else if (strcmp(RSTRING_PTR(string), "use_block") == 0) { + ISEQ_BODY(iseq)->param.flags.use_block = 1; + } else { goto unknown_arg; } @@ -9377,6 +9382,8 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } else { /* NODE_ZSUPER */ + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + int i; const rb_iseq_t *liseq = body->local_iseq; const struct rb_iseq_constant_body *const local_body = ISEQ_BODY(liseq); @@ -9510,6 +9517,7 @@ compile_yield(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i ADD_SEQ(ret, args); ADD_INSN1(ret, node, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), flag, keywords, FALSE)); + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; if (popped) { ADD_INSN(ret, node, pop); @@ -12935,7 +12943,10 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) (body->param.flags.has_block << 6) | (body->param.flags.ambiguous_param0 << 7) | (body->param.flags.accepts_no_kwarg << 8) | - (body->param.flags.ruby2_keywords << 9); + (body->param.flags.ruby2_keywords << 9) | + (body->param.flags.anon_rest << 10) | + (body->param.flags.anon_kwrest << 11) | + (body->param.flags.use_block << 12); #if IBF_ISEQ_ENABLE_LOCAL_BUFFER # define IBF_BODY_OFFSET(x) (x) @@ -13151,6 +13162,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->param.flags.ruby2_keywords = (param_flags >> 9) & 1; load_body->param.flags.anon_rest = (param_flags >> 10) & 1; load_body->param.flags.anon_kwrest = (param_flags >> 11) & 1; + load_body->param.flags.use_block = (param_flags >> 12) & 1; load_body->param.size = param_size; load_body->param.lead_num = param_lead_num; load_body->param.opt_num = param_opt_num; diff --git a/dir.rb b/dir.rb index 632c49eee93bab..42b475ab2cd82c 100644 --- a/dir.rb +++ b/dir.rb @@ -408,6 +408,7 @@ def self.[](*args, base: nil, sort: true) # specifies that patterns may match short names if they exist; Windows only. # def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true) + Primitive.attr! :use_block Primitive.dir_s_glob(pattern, flags, base, sort) end end diff --git a/gems/bundled_gems b/gems/bundled_gems index 842fbe0ce35e50..ad979c1c5c4b1e 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.22.3 https://github.com/minitest/minitest 287b35d60c8e19c11ba090efc6eeb225325a8520 +minitest 5.22.3 https://github.com/minitest/minitest ea9caafc0754b1d6236a490d59e624b53209734a power_assert 2.0.3 https://github.com/ruby/power_assert 84e85124c5014a139af39161d484156cfe87a9ed rake 13.2.1 https://github.com/ruby/rake test-unit 3.6.2 https://github.com/test-unit/test-unit diff --git a/iseq.c b/iseq.c index 3659dab2f516a7..4d4006777ea08f 100644 --- a/iseq.c +++ b/iseq.c @@ -538,6 +538,11 @@ iseq_location_setup(rb_iseq_t *iseq, VALUE name, VALUE path, VALUE realpath, int RB_OBJ_WRITE(iseq, &loc->label, name); RB_OBJ_WRITE(iseq, &loc->base_label, name); loc->first_lineno = first_lineno; + + if (ISEQ_BODY(iseq)->local_iseq == iseq && strcmp(RSTRING_PTR(name), "initialize") == 0) { + ISEQ_BODY(iseq)->param.flags.use_block = 1; + } + if (code_location) { loc->node_id = node_id; loc->code_location = *code_location; @@ -1011,6 +1016,7 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa { rb_iseq_t *iseq = iseq_alloc(); ISEQ_BODY(iseq)->prism = true; + ISEQ_BODY(iseq)->param.flags.use_block = true; // unused block warning is not supported yet if (!option) option = &COMPILE_OPTION_DEFAULT; diff --git a/lib/optparse.rb b/lib/optparse.rb index 76ff38aca3b992..1dec3fa12418e7 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -461,7 +461,7 @@ def self.candidate(key, icase = false, pat = nil, &block) candidates end - def candidate(key, icase = false, pat = nil) + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end @@ -739,7 +739,7 @@ class RequiredArgument < self # # Raises an exception if argument is not present. # - def parse(arg, argv) + def parse(arg, argv, &_) unless arg raise MissingArgument if argv.empty? arg = argv.shift diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb index c6edfb473c2d16..c688d562c3e567 100644 --- a/lib/rdoc/context.rb +++ b/lib/rdoc/context.rb @@ -710,7 +710,7 @@ def display(method_attr) # :nodoc: # This method exists to make it easy to work with Context subclasses that # aren't part of RDoc. - def each_ancestor # :nodoc: + def each_ancestor(&_) # :nodoc: end ## diff --git a/pack.rb b/pack.rb index d505eaee3570ca..b6e29c3eab4f12 100644 --- a/pack.rb +++ b/pack.rb @@ -17,6 +17,7 @@ class String # returns that array. # See {Packed Data}[rdoc-ref:packed_data.rdoc]. def unpack(fmt, offset: 0) + Primitive.attr! :use_block Primitive.pack_unpack(fmt, offset) end diff --git a/rjit_c.rb b/rjit_c.rb index 0ba33b40bf904d..b9a5bb0b5532bc 100644 --- a/rjit_c.rb +++ b/rjit_c.rb @@ -1093,6 +1093,7 @@ def C.rb_iseq_constant_body ruby2_keywords: [CType::BitField.new(1, 1), 9], anon_rest: [CType::BitField.new(1, 2), 10], anon_kwrest: [CType::BitField.new(1, 3), 11], + use_block: [CType::BitField.new(1, 4), 12], ), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, flags)")], size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, size)")], lead_num: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, lead_num)")], diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index ec06f4c50a150d..67b3e03e10e9c6 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1623,4 +1623,63 @@ def test_kwarg_eval_memory_leak end RUBY end + + def test_warn_unused_block + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + foo{} # warn + send(:foo){} # warn + b = Proc.new{} + foo(&b) # warn + RUBY + assert_equal 3, err.size + err = err.join + assert_match(/-:2: warning/, err) + assert_match(/-:3: warning/, err) + assert_match(/-:5: warning/, err) + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + 10.times{foo{}} # warn once + RUBY + assert_equal 1, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil; b = nil + foo(&b) # no warning + 1.object_id{} # no warning because it is written in C + + class C + def initialize + end + end + C.new{} # no warning + + RUBY + assert_equal 0, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + class C0 + def foo = nil + def bar = nil + def baz = nil + end + + class C1 < C0 + def foo = super + def bar = super() + def baz(&_) = super(&_) + end + + C1.new.foo{} # no warning + C1.new.bar{} # warning + C1.new.baz{} # no warning + RUBY + assert_equal 1, err.size + assert_match(/-:14: warning.+bar/, err.join) + end + end end diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 70b6bde6ed611b..44ec615405d561 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -577,7 +577,7 @@ def test_string_freeze_block begin; class String undef freeze - def freeze + def freeze(&) block_given? end end diff --git a/tool/mk_builtin_loader.rb b/tool/mk_builtin_loader.rb index 07c829124948a1..4abd497f0eae19 100644 --- a/tool/mk_builtin_loader.rb +++ b/tool/mk_builtin_loader.rb @@ -6,7 +6,7 @@ SUBLIBS = {} REQUIRED = {} -BUILTIN_ATTRS = %w[leaf inline_block] +BUILTIN_ATTRS = %w[leaf inline_block use_block] def string_literal(lit, str = []) while lit diff --git a/tool/test/testunit/test_parallel.rb b/tool/test/testunit/test_parallel.rb index f79c3a1d806e59..6882fd6c5f8f82 100644 --- a/tool/test/testunit/test_parallel.rb +++ b/tool/test/testunit/test_parallel.rb @@ -119,7 +119,7 @@ def test_done result = Marshal.load($1.chomp.unpack1("m")) assert_equal(5, result[0]) - pend "TODO: result[1] returns 17. We should investigate it" do + pend "TODO: result[1] returns 17. We should investigate it" do # TODO: misusage of pend (pend doens't use given block) assert_equal(12, result[1]) end assert_kind_of(Array,result[2]) diff --git a/trace_point.rb b/trace_point.rb index e61b4b680207bd..b136e9b399dbe6 100644 --- a/trace_point.rb +++ b/trace_point.rb @@ -94,6 +94,7 @@ class TracePoint # Access from other threads is also forbidden. # def self.new(*events) + Primitive.attr! :use_block Primitive.tracepoint_new_s(events) end @@ -131,6 +132,7 @@ def self.stat # trace.enabled? #=> true # def self.trace(*events) + Primitive.attr! :use_block Primitive.tracepoint_trace_s(events) end @@ -196,6 +198,7 @@ def self.trace(*events) # out calls by itself from :line handler, otherwise it will call itself infinitely). # def self.allow_reentry + Primitive.attr! :use_block Primitive.tracepoint_allow_reentry end @@ -258,6 +261,7 @@ def self.allow_reentry # #=> RuntimeError: access from outside # def enable(target: nil, target_line: nil, target_thread: :default) + Primitive.attr! :use_block Primitive.tracepoint_enable_m(target, target_line, target_thread) end @@ -294,6 +298,7 @@ def enable(target: nil, target_line: nil, target_thread: :default) # trace.disable { p tp.lineno } # #=> RuntimeError: access from outside def disable + Primitive.attr! :use_block Primitive.tracepoint_disable_m end diff --git a/vm_backtrace.c b/vm_backtrace.c index 6f4379ad23e85c..3fe816930da2de 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -201,8 +201,8 @@ location_lineno_m(VALUE self) VALUE rb_mod_name0(VALUE klass, bool *permanent); -static VALUE -gen_method_name(VALUE owner, VALUE name) +VALUE +rb_gen_method_name(VALUE owner, VALUE name) { bool permanent; if (RB_TYPE_P(owner, T_CLASS) || RB_TYPE_P(owner, T_MODULE)) { @@ -235,7 +235,7 @@ calculate_iseq_label(VALUE owner, const rb_iseq_t *iseq) case ISEQ_TYPE_MAIN: return ISEQ_BODY(iseq)->location.label; case ISEQ_TYPE_METHOD: - return gen_method_name(owner, ISEQ_BODY(iseq)->location.label); + return rb_gen_method_name(owner, ISEQ_BODY(iseq)->location.label); case ISEQ_TYPE_BLOCK: case ISEQ_TYPE_PLAIN: { int level = 0; @@ -269,7 +269,7 @@ static VALUE location_label(rb_backtrace_location_t *loc) { if (loc->cme && loc->cme->def->type == VM_METHOD_TYPE_CFUNC) { - return gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + return rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); } else { VALUE owner = Qnil; @@ -457,7 +457,7 @@ location_to_str(rb_backtrace_location_t *loc) file = GET_VM()->progname; lineno = 0; } - name = gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + name = rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); } else { file = rb_iseq_path(loc->iseq); diff --git a/vm_core.h b/vm_core.h index 2a9d5f906f101f..57d90b343f31c9 100644 --- a/vm_core.h +++ b/vm_core.h @@ -418,6 +418,7 @@ struct rb_iseq_constant_body { unsigned int ruby2_keywords: 1; unsigned int anon_rest: 1; unsigned int anon_kwrest: 1; + unsigned int use_block: 1; } flags; unsigned int size; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index fe038e86181aaa..68a74a3e337208 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2966,6 +2966,57 @@ vm_call_single_noarg_leaf_builtin(rb_execution_context_t *ec, rb_control_frame_t return builtin_invoker0(ec, calling->recv, NULL, (rb_insn_func_t)bf->func_ptr); } +VALUE rb_gen_method_name(VALUE owner, VALUE name); // in vm_backtrace.c + +static void +warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, void *pc) +{ + static st_table *dup_check_table = NULL; + + st_data_t key = 0; + union { + VALUE v; + unsigned char b[SIZEOF_VALUE]; + } k1 = { + .v = (VALUE)pc, + }, k2 = { + .v = (VALUE)cme->def, + }; + + // make unique key from pc and me->def pointer + for (int i=0; idef); + fprintf(stderr, "key:%p\n", (void *)key); + } + + if (!dup_check_table) { + dup_check_table = st_init_numtable(); + } + + // duplication check + if (st_insert(dup_check_table, key, 1)) { + // already shown + } + else { + 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 passed block for '%"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); + } + } +} + static inline int vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size) @@ -2974,6 +3025,12 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const struct rb_callcache *cc = calling->cc; bool cacheable_ci = vm_ci_markable(ci); + if (UNLIKELY(!ISEQ_BODY(iseq)->param.flags.use_block && + calling->block_handler != VM_BLOCK_HANDLER_NONE && + !(vm_ci_flag(calling->cd->ci) & VM_CALL_SUPER))) { + warn_unused_block(vm_cc_cme(cc), iseq, (void *)ec->cfp->pc); + } + if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) { if (LIKELY(rb_simple_iseq_p(iseq))) { rb_control_frame_t *cfp = ec->cfp; From fc363944b40e4031b447f91fa897935620d43ecd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:05:43 +0900 Subject: [PATCH 118/135] [ruby/optparse] bump up to 0.5.0 https://github.com/ruby/optparse/commit/f5018a8b1c --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 1dec3fa12418e7..069c3e436e51e5 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # class OptionParser # The version string - OptionParser::Version = "0.4.0" + OptionParser::Version = "0.5.0" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From b6a10a15180250cef9ec2bacedb71fa392ac0b8d Mon Sep 17 00:00:00 2001 From: git Date: Mon, 15 Apr 2024 05:07:51 +0000 Subject: [PATCH 119/135] Update default gems list at fc363944b40e4031b447f91fa89793 [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index ff08c4e56604ae..34fa51ce9d051a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -44,6 +44,7 @@ The following default gems are updated. * irb 1.12.0 * json 2.7.2 * net-http 0.4.1 +* optparse 0.5.0 * prism 0.25.0 * rdoc 6.6.3.1 * reline 0.5.1 From 145cced9bcb6a52fccfa0c669121bd07d3b3ff74 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 15 Apr 2024 14:22:40 +0900 Subject: [PATCH 120/135] fix incorrect warning. `super()` (not zsuper) passes the passed block and it can be used. ```ruby class C0 def foo; yield; end end class C1 < C0 def foo; super(); end end C1.new.foo{p :block} #=> :block ``` --- compile.c | 4 ++-- test/ruby/test_method.rb | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compile.c b/compile.c index 2a7bdc2c99a941..30321a7af80c5f 100644 --- a/compile.c +++ b/compile.c @@ -9372,6 +9372,8 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i INIT_ANCHOR(args); ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + if (type == NODE_SUPER) { VALUE vargc = setup_args(iseq, args, RNODE_SUPER(node)->nd_args, &flag, &keywords); CHECK(!NIL_P(vargc)); @@ -9382,8 +9384,6 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } else { /* NODE_ZSUPER */ - ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; - int i; const rb_iseq_t *liseq = body->local_iseq; const struct rb_iseq_constant_body *const local_body = ISEQ_BODY(liseq); diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 67b3e03e10e9c6..34b58a1f51016e 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1666,20 +1666,25 @@ class C0 def foo = nil def bar = nil def baz = nil + def qux = nil end class C1 < C0 def foo = super def bar = super() def baz(&_) = super(&_) + def qux = super(&nil) end C1.new.foo{} # no warning - C1.new.bar{} # warning + C1.new.bar{} # no warning C1.new.baz{} # no warning + # C1.new.qux{} # TODO: warning line:16 but not supported yet. RUBY - assert_equal 1, err.size - assert_match(/-:14: warning.+bar/, err.join) + assert_equal 0, err.size + # TODO + # assert_equal 1, err.size + # assert_match(/-:16: warning.+qux/, err.join) end end end From 9a57b047033034c30a3351d08ad648735299c8cf Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Mon, 15 Apr 2024 17:06:03 +0900 Subject: [PATCH 121/135] `super{}` doesn't use block `super(){}`, `super{}` and `super(&b)` doesn't use the given block so warn unused block warning when calling a method which doesn't use block with above `super` expressions. e.g.: `def f = super{B1}` (warn on `f{B2}` because `B2` is not used. --- compile.c | 10 +++++++++- test/ruby/test_method.rb | 38 ++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/compile.c b/compile.c index 30321a7af80c5f..0fb3b855838d36 100644 --- a/compile.c +++ b/compile.c @@ -9369,10 +9369,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i unsigned int flag = 0; struct rb_callinfo_kwarg *keywords = NULL; const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; + int use_block = 1; INIT_ANCHOR(args); ISEQ_COMPILE_DATA(iseq)->current_block = NULL; - ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; if (type == NODE_SUPER) { VALUE vargc = setup_args(iseq, args, RNODE_SUPER(node)->nd_args, &flag, &keywords); @@ -9381,6 +9381,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i if ((flag & VM_CALL_ARGS_BLOCKARG) && (flag & VM_CALL_KW_SPLAT) && !(flag & VM_CALL_KW_SPLAT_MUT)) { ADD_INSN(args, node, splatkw); } + + if (flag & VM_CALL_ARGS_BLOCKARG) { + use_block = 0; + } } else { /* NODE_ZSUPER */ @@ -9474,6 +9478,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } } + if (use_block && parent_block == NULL) { + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + } + flag |= VM_CALL_SUPER | VM_CALL_FCALL; if (type == NODE_ZSUPER) flag |= VM_CALL_ZSUPER; ADD_INSN(ret, node, putself); diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 34b58a1f51016e..a41704cf069055 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1663,28 +1663,34 @@ def initialize assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| class C0 - def foo = nil - def bar = nil - def baz = nil - def qux = nil + def f1 = nil + def f2 = nil + def f3 = nil + def f4 = nil + def f5 = nil + def f6 = nil end class C1 < C0 - def foo = super - def bar = super() - def baz(&_) = super(&_) - def qux = super(&nil) + def f1 = super # zsuper / use + def f2 = super() # super / use + def f3(&_) = super(&_) # super / use + def f4 = super(&nil) # super / unuse + def f5 = super(){} # super / unuse + def f6 = super{} # zsuper / unuse end - C1.new.foo{} # no warning - C1.new.bar{} # no warning - C1.new.baz{} # no warning - # C1.new.qux{} # TODO: warning line:16 but not supported yet. + C1.new.f1{} # no warning + C1.new.f2{} # no warning + C1.new.f3{} # no warning + C1.new.f4{} # warning + C1.new.f5{} # warning + C1.new.f6{} # warning RUBY - assert_equal 0, err.size - # TODO - # assert_equal 1, err.size - # assert_match(/-:16: warning.+qux/, err.join) + assert_equal 3, err.size, err.join("\n") + assert_match(/-:22: warning.+f4/, err.join) + assert_match(/-:23: warning.+f5/, err.join) + assert_match(/-:24: warning.+f6/, err.join) end end end From bb1c3418d0fd3235c678ad68f7b45d32f8183a3f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 15 Apr 2024 09:04:51 +0200 Subject: [PATCH 122/135] Add more assertions in `test_uplus_minus` Not along after 1b830740ba8371c4bcfdfc6eb2cb7e0ae81a84e0 CI started to rarely fail this test: ``` TestString#test_uplus_minus: Test::Unit::AssertionFailedError: uminus deduplicates [Feature #13077]. 1) Failure: TestString#test_uplus_minus [/tmp/ruby/src/trunk/test/ruby/test_string.rb:3368]: ``` It's unclear what is going on, but one possibility is that `"bar".freeze` might no longer compile correctly. Another possibility is that another test redefine `String#freeze`, causing `opt_str_freeze` to no longer return an `fstring`. --- test/ruby/test_string.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 9ff3791e7de986..e395d75e6dc3da 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3356,7 +3356,11 @@ def test_uplus_minus assert_same(str, +str) assert_not_same(str, -str) + require 'objspace' + str = "bar".freeze + assert_includes ObjectSpace.dump(str), '"fstring":true' + assert_predicate(str, :frozen?) assert_not_predicate(+str, :frozen?) assert_predicate(-str, :frozen?) @@ -3364,8 +3368,8 @@ def test_uplus_minus assert_not_same(str, +str) assert_same(str, -str) - bar = %w(b a r).join('') - assert_same(str, -bar, "uminus deduplicates [Feature #13077]") + bar = -%w(b a r).join('') + assert_same(str, bar, "uminus deduplicates [Feature #13077] #{ObjectSpace.dump(bar)}") end def test_uminus_frozen From 9b1e97b211565b605b8eb7fab277efe117fe2604 Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Thu, 28 Mar 2024 10:26:42 +0900 Subject: [PATCH 123/135] [Universal parser] DeVALUE of p->debug_lines and ast->body.script_lines This patch is part of universal parser work. ## Summary - Decouple VALUE from members below: - `(struct parser_params *)->debug_lines` - `(rb_ast_t *)->body.script_lines` - Instead, they are now `rb_parser_ary_t *` - They can also be a `(VALUE)FIXNUM` as before to hold line count - `ISEQ_BODY(iseq)->variable.script_lines` remains VALUE - In order to do this, - Add `VALUE script_lines` param to `rb_iseq_new_with_opt()` - Introduce `rb_parser_build_script_lines_from()` to convert `rb_parser_ary_t *` into `VALUE` ## Other details - Extend `rb_parser_ary_t *`. It previously could only store `rb_parser_ast_token *`, now can store script_lines, too - Change tactics of building the top-level `SCRIPT_LINES__` in `yycompile0()` - Before: While parsing, each line of the script is added to `SCRIPT_LINES__[path]` - After: After `yyparse(p)`, `SCRIPT_LINES__[path]` will be built from `p->debug_lines` - Remove the second parameter of `rb_parser_set_script_lines()` to make it simple - Introduce `script_lines_free()` to be called from `rb_ast_free()` because the GC no longer takes care of the script_lines - Introduce `rb_parser_string_deep_copy()` in parse.y to maintain script_lines when `rb_ruby_parser_free()` called - With regard to this, please see *Future tasks* below ## Future tasks - Decouple IMEMO from `rb_ast_t *` - This lifts the five-members-restriction of Ruby object, - So we will be able to move the ownership of the `lex.string_buffer` from parser to AST - Then we remove `rb_parser_string_deep_copy()` to make the whole thing simple --- ast.c | 12 +-- compile.c | 10 ++- imemo.c | 2 +- internal/parse.h | 2 +- internal/ruby_parser.h | 4 +- iseq.c | 40 ++++++---- mini_builtin.c | 2 +- node.c | 17 +++-- node.h | 1 - parse.y | 164 ++++++++++++++++++++++++++++------------ ruby.c | 7 +- ruby_parser.c | 59 +++++++++++---- rubyparser.h | 14 +++- template/prelude.c.tmpl | 3 +- vm.c | 2 +- vm_core.h | 3 +- vm_eval.c | 2 +- 17 files changed, 234 insertions(+), 110 deletions(-) diff --git a/ast.c b/ast.c index 70f298c7f8adf5..a4c57b898be6b6 100644 --- a/ast.c +++ b/ast.c @@ -97,7 +97,7 @@ rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE StringValue(str); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); @@ -120,7 +120,7 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VAL f = rb_file_open_str(path, "r"); rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); @@ -148,7 +148,7 @@ rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, V array = rb_check_array_type(array); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); @@ -806,9 +806,9 @@ ast_node_script_lines(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); - VALUE ret = data->ast->body.script_lines; - if (!RB_TYPE_P(ret, T_ARRAY)) return Qnil; - return ret; + rb_parser_ary_t *ret = data->ast->body.script_lines; + if (!ret || FIXNUM_P((VALUE)ret)) return Qnil; + return rb_parser_build_script_lines_from(ret); } #include "ast.rbinc" diff --git a/compile.c b/compile.c index 0fb3b855838d36..d5fa6a5c7451be 100644 --- a/compile.c +++ b/compile.c @@ -1483,7 +1483,7 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node, ast.root = node; ast.frozen_string_literal = -1; ast.coverage_enabled = -1; - ast.script_lines = ISEQ_BODY(iseq)->variable.script_lines; + ast.script_lines = NULL; debugs("[new_child_iseq]> ---------------------------------------\n"); int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth; @@ -1491,7 +1491,8 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node, rb_iseq_path(iseq), rb_iseq_realpath(iseq), line_no, parent, isolated_depth ? isolated_depth + 1 : 0, - type, ISEQ_COMPILE_DATA(iseq)->option); + type, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); debugs("[new_child_iseq]< ---------------------------------------\n"); return ret_iseq; } @@ -8740,14 +8741,15 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N .root = RNODE(&scope_node), .frozen_string_literal = -1, .coverage_enabled = -1, - .script_lines = ISEQ_BODY(iseq)->variable.script_lines, + .script_lines = NULL }; ISEQ_BODY(iseq)->mandatory_only_iseq = rb_iseq_new_with_opt(&ast, rb_iseq_base_label(iseq), rb_iseq_path(iseq), rb_iseq_realpath(iseq), nd_line(line_node), NULL, 0, - ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option); + ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); ALLOCV_END(idtmp); return COMPILE_OK; diff --git a/imemo.c b/imemo.c index 0031b3322ccf8b..8403859146cc67 100644 --- a/imemo.c +++ b/imemo.c @@ -274,7 +274,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) { switch (imemo_type(obj)) { case imemo_ast: - rb_ast_mark_and_move((rb_ast_t *)obj, reference_updating); + // TODO: Make AST decoupled from IMEMO break; case imemo_callcache: { diff --git a/internal/parse.h b/internal/parse.h index 20367730d1b99c..80328686c1f8c4 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -51,7 +51,7 @@ size_t rb_ruby_parser_memsize(const void *ptr); void rb_ruby_parser_set_options(rb_parser_t *p, int print, int loop, int chomp, int split); rb_parser_t *rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, int main); -void rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines_array); +void rb_ruby_parser_set_script_lines(rb_parser_t *p); void rb_ruby_parser_error_tolerant(rb_parser_t *p); rb_ast_t* rb_ruby_parser_compile_file_path(rb_parser_t *p, VALUE fname, VALUE file, int start); void rb_ruby_parser_keep_tokens(rb_parser_t *p); diff --git a/internal/ruby_parser.h b/internal/ruby_parser.h index 0a00075211fba8..f0cec8666886f9 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -39,9 +39,11 @@ RUBY_SYMBOL_EXPORT_END VALUE rb_parser_end_seen_p(VALUE); VALUE rb_parser_encoding(VALUE); VALUE rb_parser_set_yydebug(VALUE, VALUE); +VALUE rb_parser_build_script_lines_from(rb_parser_ary_t *script_lines); +void rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *script_lines); void rb_parser_set_options(VALUE, int, int, int, int); void *rb_parser_load_file(VALUE parser, VALUE name); -void rb_parser_set_script_lines(VALUE vparser, VALUE lines_array); +void rb_parser_set_script_lines(VALUE vparser); void rb_parser_error_tolerant(VALUE vparser); void rb_parser_keep_tokens(VALUE vparser); diff --git a/iseq.c b/iseq.c index 4d4006777ea08f..6d4fa5bd336e80 100644 --- a/iseq.c +++ b/iseq.c @@ -839,20 +839,21 @@ rb_iseq_new(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent, enum rb_iseq_type type) { return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, - 0, type, &COMPILE_OPTION_DEFAULT); + 0, type, &COMPILE_OPTION_DEFAULT, + Qnil); } static int ast_line_count(const rb_ast_body_t *ast) { - if (ast->script_lines == Qfalse) { + if (ast->script_lines == NULL) { // this occurs when failed to parse the source code with a syntax error return 0; } - if (RB_TYPE_P(ast->script_lines, T_ARRAY)){ - return (int)RARRAY_LEN(ast->script_lines); + if (!FIXNUM_P((VALUE)ast->script_lines)) { + return (int)ast->script_lines->len; } - return FIX2INT(ast->script_lines); + return FIX2INT((VALUE)ast->script_lines); } static VALUE @@ -888,7 +889,8 @@ rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath iseq_new_setup_coverage(path, ast, 0); return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, 0, - ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT); + ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT, + Qnil); } /** @@ -910,7 +912,8 @@ rb_iseq_new_main(const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_ return rb_iseq_new_with_opt(ast, rb_fstring_lit("
"), path, realpath, 0, - parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE); + parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE, + Qnil); } /** @@ -938,7 +941,8 @@ rb_iseq_new_eval(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpat } return rb_iseq_new_with_opt(ast, name, path, realpath, first_lineno, - parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT); + parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT, + Qnil); } rb_iseq_t * @@ -966,7 +970,8 @@ iseq_translate(rb_iseq_t *iseq) rb_iseq_t * rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth, - enum rb_iseq_type type, const rb_compile_option_t *option) + enum rb_iseq_type type, const rb_compile_option_t *option, + VALUE script_lines) { const NODE *node = ast ? ast->root : 0; /* TODO: argument check */ @@ -979,10 +984,11 @@ rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE rea option = set_compile_option_from_ast(&new_opt, ast); } - VALUE script_lines = Qnil; - - if (ast && !FIXNUM_P(ast->script_lines) && ast->script_lines) { - script_lines = ast->script_lines; + if (!NIL_P(script_lines)) { + // noop + } + else if (ast && !FIXNUM_P((VALUE)ast->script_lines) && ast->script_lines) { + script_lines = rb_parser_build_script_lines_from(ast->script_lines); } else if (parent) { script_lines = ISEQ_BODY(parent)->variable.script_lines; @@ -1225,7 +1231,7 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V const rb_iseq_t *outer_scope = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); VALUE outer_scope_v = (VALUE)outer_scope; rb_parser_set_context(parser, outer_scope, FALSE); - rb_parser_set_script_lines(parser, RBOOL(ruby_vm_keep_script_lines)); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); RB_GC_GUARD(outer_scope_v); ast = (*parse)(parser, file, src, ln); } @@ -1236,7 +1242,8 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V } else { iseq = rb_iseq_new_with_opt(&ast->body, name, file, realpath, ln, - NULL, 0, ISEQ_TYPE_TOP, &option); + NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil); rb_ast_dispose(ast); } @@ -1627,7 +1634,8 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) ret = iseqw_new(rb_iseq_new_with_opt(&ast->body, rb_fstring_lit("
"), file, rb_realpath_internal(Qnil, file, 1), - 1, NULL, 0, ISEQ_TYPE_TOP, &option)); + 1, NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil)); rb_ast_dispose(ast); rb_vm_pop_frame(ec); diff --git a/mini_builtin.c b/mini_builtin.c index dce822a86c176d..38b0ca8d818948 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -39,7 +39,7 @@ builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *ta .coverage_enabled = FALSE, .debug_level = 0, }; - const rb_iseq_t *iseq = rb_iseq_new_with_opt(&ast->body, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization); + const rb_iseq_t *iseq = rb_iseq_new_with_opt(&ast->body, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization, Qnil); GET_VM()->builtin_function_table = NULL; rb_ast_dispose(ast); diff --git a/node.c b/node.c index 79520f0a1e942c..8a6b55b0b5808a 100644 --- a/node.c +++ b/node.c @@ -20,12 +20,13 @@ #include "internal.h" #include "internal/hash.h" -#include "internal/variable.h" #include "ruby/ruby.h" #include "vm_core.h" #endif +#include "internal/variable.h" + #define NODE_BUF_DEFAULT_SIZE (sizeof(struct RNode) * 16) static void @@ -344,18 +345,24 @@ iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, vo } } -void -rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating) +static void +script_lines_free(rb_ast_t *ast, rb_parser_ary_t *script_lines) { - if (ast->node_buffer) { - if (ast->body.script_lines) rb_gc_mark_and_move(&ast->body.script_lines); + for (long i = 0; i < script_lines->len; i++) { + parser_string_free(ast, (rb_parser_string_t *)script_lines->data[i]); } + xfree(script_lines->data); + xfree(script_lines); } void rb_ast_free(rb_ast_t *ast) { if (ast->node_buffer) { + if (ast->body.script_lines && !FIXNUM_P((VALUE)ast->body.script_lines)) { + script_lines_free(ast, ast->body.script_lines); + ast->body.script_lines = NULL; + } rb_node_buffer_free(ast, ast->node_buffer); ast->node_buffer = 0; } diff --git a/node.h b/node.h index d5522c82eca355..bcc7e451d2c5ee 100644 --- a/node.h +++ b/node.h @@ -56,7 +56,6 @@ void rb_ast_dispose(rb_ast_t*); const char *ruby_node_name(int node); void rb_node_init(NODE *n, enum node_type type); -void rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating); void rb_ast_update_references(rb_ast_t*); void rb_ast_free(rb_ast_t*); NODE *rb_ast_newnode(rb_ast_t*, enum node_type type, size_t size, size_t alignment); diff --git a/parse.y b/parse.y index 1a052d475742f4..a87be73e3cfc61 100644 --- a/parse.y +++ b/parse.y @@ -86,6 +86,10 @@ VALUE rb_io_gets_internal(VALUE io); static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); +#ifndef RIPPER +static rb_parser_string_t *rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *original); +#endif + static int node_integer_cmp(rb_node_integer_t *n1, rb_node_integer_t *n2) { @@ -582,7 +586,7 @@ struct parser_params { unsigned int keep_tokens: 1; VALUE error_buffer; - VALUE debug_lines; + rb_parser_ary_t *debug_lines; /* * Store specific keyword locations to generate dummy end token. * Refer to the tail of list element. @@ -2559,15 +2563,19 @@ rb_parser_ary_extend(rb_parser_t *p, rb_parser_ary_t *ary, long len) long i; if (ary->capa < len) { ary->capa = len; - ary->data = xrealloc(ary->data, sizeof(void *) * len); + ary->data = (rb_parser_ary_data *)xrealloc(ary->data, sizeof(rb_parser_ary_data) * len); for (i = ary->len; i < len; i++) { ary->data[i] = 0; } } } +/* + * Do not call this directly. + * Use rb_parser_ary_new_capa_for_script_line() or rb_parser_ary_new_capa_for_ast_token() instead. + */ static rb_parser_ary_t * -rb_parser_ary_new_capa(rb_parser_t *p, long len) +parser_ary_new_capa(rb_parser_t *p, long len) { if (len < 0) { rb_bug("negative array size (or size too big): %ld", len); @@ -2576,17 +2584,36 @@ rb_parser_ary_new_capa(rb_parser_t *p, long len) ary->len = 0; ary->capa = len; if (0 < len) { - ary->data = (rb_parser_ast_token_t **)xcalloc(len, sizeof(rb_parser_ast_token_t *)); + ary->data = (rb_parser_ary_data *)xcalloc(len, sizeof(rb_parser_ary_data)); } else { ary->data = NULL; } return ary; } -#define rb_parser_ary_new2 rb_parser_ary_new_capa static rb_parser_ary_t * -rb_parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +rb_parser_ary_new_capa_for_script_line(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_SCRIPT_LINE; + return ary; +} + +static rb_parser_ary_t * +rb_parser_ary_new_capa_for_ast_token(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_AST_TOKEN; + return ary; +} + +/* + * Do not call this directly. + * Use rb_parser_ary_push_script_line() or rb_parser_ary_push_ast_token() instead. + */ +static rb_parser_ary_t * +parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ary_data val) { if (ary->len == ary->capa) { rb_parser_ary_extend(p, ary, ary->len == 0 ? 1 : ary->len * 2); @@ -2595,6 +2622,24 @@ rb_parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t * return ary; } +static rb_parser_ary_t * +rb_parser_ary_push_ast_token(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_AST_TOKEN) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + +static rb_parser_ary_t * +rb_parser_ary_push_script_line(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_string_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + static void rb_parser_ast_token_free(rb_parser_t *p, rb_parser_ast_token_t *token) { @@ -2604,12 +2649,24 @@ rb_parser_ast_token_free(rb_parser_t *p, rb_parser_ast_token_t *token) } static void -rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) +rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) { - for (long i = 0; i < tokens->len; i++) { - rb_parser_ast_token_free(p, tokens->data[i]); + void (*free_func)(rb_parser_t *, rb_parser_ary_data) = NULL; + switch (ary->data_type) { + case PARSER_ARY_DATA_AST_TOKEN: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_ast_token_free; + break; + case PARSER_ARY_DATA_SCRIPT_LINE: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_string_free; + break; + default: + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + break; } - xfree(tokens); + for (long i = 0; i < ary->len; i++) { + free_func(p, ary->data[i]); + } + xfree(ary); } #endif /* !RIPPER */ @@ -7144,7 +7201,7 @@ parser_append_tokens(struct parser_params *p, rb_parser_string_t *str, enum yyto token->str = str; token->loc.beg_pos = p->yylloc->beg_pos; token->loc.end_pos = p->yylloc->end_pos; - rb_parser_ary_push(p, p->tokens, token); + rb_parser_ary_push_ast_token(p, p->tokens, token); p->token_id++; if (p->debug) { @@ -7656,22 +7713,12 @@ yycompile0(VALUE arg) struct parser_params *p = (struct parser_params *)arg; int cov = FALSE; - if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string)) { - if (p->debug_lines && p->ruby_sourceline > 0) { - VALUE str = rb_default_rs; - n = p->ruby_sourceline; - do { - rb_ary_push(p->debug_lines, str); - } while (--n); - } - - if (!e_option_supplied(p)) { - cov = TRUE; - } + if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string) && !e_option_supplied(p)) { + cov = TRUE; } if (p->debug_lines) { - RB_OBJ_WRITE(p->ast, &p->ast->body.script_lines, p->debug_lines); + p->ast->body.script_lines = p->debug_lines; } parser_prepare(p); @@ -7682,6 +7729,8 @@ yycompile0(VALUE arg) RUBY_DTRACE_PARSE_HOOK(BEGIN); n = yyparse(p); RUBY_DTRACE_PARSE_HOOK(END); + + rb_parser_aset_script_lines_for(p->ruby_sourcefile_string, p->debug_lines); p->debug_lines = 0; xfree(p->lex.strterm); @@ -7715,7 +7764,7 @@ yycompile0(VALUE arg) } } p->ast->body.root = tree; - if (!p->ast->body.script_lines) p->ast->body.script_lines = INT2FIX(p->line_count); + if (!p->ast->body.script_lines) p->ast->body.script_lines = (rb_parser_ary_t *)INT2FIX(p->line_count); return TRUE; } @@ -7975,9 +8024,9 @@ nextline(struct parser_params *p, int set_encoding) } #ifndef RIPPER if (p->debug_lines) { - VALUE v = rb_str_new_mutable_parser_string(str); - if (set_encoding) rb_enc_associate(v, p->enc); - rb_ary_push(p->debug_lines, v); + if (set_encoding) rb_parser_enc_associate(p, str, p->enc); + rb_parser_string_t *copy = rb_parser_string_deep_copy(p, str); + rb_parser_ary_push_script_line(p, p->debug_lines, copy); } #endif p->cr_seen = FALSE; @@ -9653,10 +9702,9 @@ parser_set_encode(struct parser_params *p, const char *name) p->enc = enc; #ifndef RIPPER if (p->debug_lines) { - VALUE lines = p->debug_lines; - long i, n = RARRAY_LEN(lines); - for (i = 0; i < n; ++i) { - rb_enc_associate_index(RARRAY_AREF(lines, i), idx); + long i; + for (i = 0; i < p->debug_lines->len; i++) { + rb_parser_enc_associate(p, p->debug_lines->data[i], enc); } } #endif @@ -12870,6 +12918,19 @@ string_literal_head(struct parser_params *p, enum node_type htype, NODE *head) return lit; } +#ifndef RIPPER +static rb_parser_string_t * +rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *orig) +{ + rb_parser_string_t *copy; + if (!orig) return NULL; + copy = rb_parser_string_new(p, PARSER_STRING_PTR(orig), PARSER_STRING_LEN(orig)); + copy->coderange = orig->coderange; + copy->enc = orig->enc; + return copy; +} +#endif + /* concat two string literals */ static NODE * literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *loc) @@ -15826,7 +15887,6 @@ rb_ruby_parser_mark(void *ptr) rb_gc_mark(p->ruby_sourcefile_string); rb_gc_mark((VALUE)p->ast); #ifndef RIPPER - rb_gc_mark(p->debug_lines); rb_gc_mark(p->error_buffer); #else rb_gc_mark(p->value); @@ -15848,7 +15908,7 @@ rb_ruby_parser_free(void *ptr) #ifndef RIPPER if (p->tokens) { - rb_parser_tokens_free(p, p->tokens); + rb_parser_ary_free(p, p->tokens); } #endif @@ -15948,19 +16008,9 @@ rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, in } void -rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines) +rb_ruby_parser_set_script_lines(rb_parser_t *p) { - if (!RTEST(lines)) { - lines = Qfalse; - } - else if (lines == Qtrue) { - lines = rb_ary_new(); - } - else { - Check_Type(lines, T_ARRAY); - rb_ary_modify(lines); - } - p->debug_lines = lines; + p->debug_lines = rb_parser_ary_new_capa_for_script_line(p, 10); } void @@ -15973,7 +16023,7 @@ void rb_ruby_parser_keep_tokens(rb_parser_t *p) { p->keep_tokens = 1; - p->tokens = rb_parser_ary_new_capa(p, 10); + p->tokens = rb_parser_ary_new_capa_for_ast_token(p, 10); } #ifndef UNIVERSAL_PARSER @@ -16045,12 +16095,12 @@ rb_parser_error_tolerant(VALUE vparser) } void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) +rb_parser_set_script_lines(VALUE vparser) { struct parser_params *p; TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_script_lines(p, lines); + rb_ruby_parser_set_script_lines(p); } void @@ -16100,6 +16150,22 @@ rb_parser_set_yydebug(VALUE self, VALUE flag) rb_ruby_parser_set_yydebug(p, RTEST(flag)); return flag; } + +void +rb_set_script_lines_for(VALUE self, VALUE path) +{ + struct parser_params *p; + VALUE hash; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return; + hash = rb_const_get_at(rb_cObject, script_lines); + if (RB_TYPE_P(hash, T_HASH)) { + rb_hash_aset(hash, path, Qtrue); + TypedData_Get_Struct(self, struct parser_params, &parser_data_type, p); + rb_ruby_parser_set_script_lines(p); + } +} #endif /* !UNIVERSAL_PARSER */ VALUE diff --git a/ruby.c b/ruby.c index 323446089429f4..fb60551c3fc050 100644 --- a/ruby.c +++ b/ruby.c @@ -2592,7 +2592,7 @@ struct load_file_arg { VALUE f; }; -VALUE rb_script_lines_for(VALUE path); +void rb_set_script_lines_for(VALUE vparser, VALUE path); static VALUE load_file_internal(VALUE argp_v) @@ -2697,10 +2697,7 @@ load_file_internal(VALUE argp_v) rb_parser_set_options(parser, opt->do_print, opt->do_loop, opt->do_line, opt->do_split); - VALUE lines = rb_script_lines_for(orig_fname); - if (!NIL_P(lines)) { - rb_parser_set_script_lines(parser, lines); - } + rb_set_script_lines_for(parser, orig_fname); if (NIL_P(f)) { f = rb_str_new(0, 0); diff --git a/ruby_parser.c b/ruby_parser.c index 1991735af438c4..5d9c6c938f35db 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -622,12 +622,12 @@ rb_parser_set_context(VALUE vparser, const struct rb_iseq_struct *base, int main } void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) +rb_parser_set_script_lines(VALUE vparser) { struct ruby_parser *parser; TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - rb_ruby_parser_set_script_lines(parser->parser_params, lines); + rb_ruby_parser_set_script_lines(parser->parser_params); } void @@ -727,8 +727,39 @@ rb_parser_set_yydebug(VALUE vparser, VALUE flag) rb_ruby_parser_set_yydebug(parser->parser_params, RTEST(flag)); return flag; } + +void +rb_set_script_lines_for(VALUE vparser, VALUE path) +{ + struct ruby_parser *parser; + VALUE hash; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return; + hash = rb_const_get_at(rb_cObject, script_lines); + if (RB_TYPE_P(hash, T_HASH)) { + rb_hash_aset(hash, path, Qtrue); + TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); + rb_ruby_parser_set_script_lines(parser->parser_params); + } +} #endif +VALUE +rb_parser_build_script_lines_from(rb_parser_ary_t *lines) +{ + int i; + if (lines->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type (%d) for script lines", lines->data_type); + } + VALUE script_lines = rb_ary_new_capa(lines->len); + for (i = 0; i < lines->len; i++) { + rb_parser_string_t *str = (rb_parser_string_t *)lines->data[i]; + rb_ary_push(script_lines, rb_enc_str_new(str->ptr, str->len, str->enc)); + } + return script_lines; +} + VALUE rb_str_new_parser_string(rb_parser_string_t *str) { @@ -935,15 +966,17 @@ rb_node_encoding_val(const NODE *node) return rb_enc_from_encoding(RNODE_ENCODING(node)->enc); } -VALUE -rb_script_lines_for(VALUE path) -{ - VALUE hash, lines; - ID script_lines; - CONST_ID(script_lines, "SCRIPT_LINES__"); - if (!rb_const_defined_at(rb_cObject, script_lines)) return Qnil; - hash = rb_const_get_at(rb_cObject, script_lines); - if (!RB_TYPE_P(hash, T_HASH)) return Qnil; - rb_hash_aset(hash, path, lines = rb_ary_new()); - return lines; +void +rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *lines) +{ + VALUE hash, script_lines; + ID script_lines_id; + if (NIL_P(path) || !lines || FIXNUM_P((VALUE)lines)) return; + CONST_ID(script_lines_id, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines_id)) return; + hash = rb_const_get_at(rb_cObject, script_lines_id); + if (!RB_TYPE_P(hash, T_HASH)) return; + if (rb_hash_lookup(hash, path) == Qnil) return; + script_lines = rb_parser_build_script_lines_from(lines); + rb_hash_aset(hash, path, script_lines); } diff --git a/rubyparser.h b/rubyparser.h index c51b9ee44acf78..d36e8dcede032f 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -219,8 +219,16 @@ typedef struct rb_parser_ast_token { /* * Array-like object for parser */ +typedef void* rb_parser_ary_data; + +enum rb_parser_ary_data_type { + PARSER_ARY_DATA_AST_TOKEN, + PARSER_ARY_DATA_SCRIPT_LINE +}; + typedef struct rb_parser_ary { - rb_parser_ast_token_t **data; + enum rb_parser_ary_data_type data_type; + rb_parser_ary_data *data; long len; // current size long capa; // capacity } rb_parser_ary_t; @@ -1201,10 +1209,10 @@ typedef struct node_buffer_struct node_buffer_t; /* T_IMEMO/ast */ typedef struct rb_ast_body_struct { const NODE *root; - VALUE script_lines; + rb_parser_ary_t *script_lines; // script_lines is either: // - a Fixnum that represents the line count of the original source, or - // - an Array that contains the lines of the original source + // - an rb_parser_ary_t* that contains the lines of the original source signed int frozen_string_literal:2; /* -1: not specified, 0: false, 1: true */ signed int coverage_enabled:2; /* -1: not specified, 0: false, 1: true */ } rb_ast_body_t; diff --git a/template/prelude.c.tmpl b/template/prelude.c.tmpl index 74f6c08da7973e..dc0a143004c0a5 100644 --- a/template/prelude.c.tmpl +++ b/template/prelude.c.tmpl @@ -198,7 +198,8 @@ prelude_eval(VALUE code, VALUE name, int line) rb_ast_t *ast = prelude_ast(name, code, line); rb_iseq_eval(rb_iseq_new_with_opt(&ast->body, name, name, Qnil, line, - NULL, 0, ISEQ_TYPE_TOP, &optimization)); + NULL, 0, ISEQ_TYPE_TOP, &optimization, + Qnil)); rb_ast_dispose(ast); } COMPILER_WARNING_POP diff --git a/vm.c b/vm.c index 328187c790a327..e7335aa1bd1e82 100644 --- a/vm.c +++ b/vm.c @@ -1479,7 +1479,7 @@ rb_binding_add_dynavars(VALUE bindval, rb_binding_t *bind, int dyncount, const I ast.root = RNODE(&tmp_node); ast.frozen_string_literal = -1; ast.coverage_enabled = -1; - ast.script_lines = INT2FIX(-1); + ast.script_lines = (rb_parser_ary_t *)INT2FIX(-1); if (base_iseq) { iseq = rb_iseq_new(&ast, ISEQ_BODY(base_iseq)->location.label, path, realpath, base_iseq, ISEQ_TYPE_EVAL); diff --git a/vm_core.h b/vm_core.h index 57d90b343f31c9..9873ada2d57197 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1201,7 +1201,8 @@ rb_iseq_t *rb_iseq_new_top (const rb_ast_body_t *ast, VALUE name, VALUE path rb_iseq_t *rb_iseq_new_main (const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt); rb_iseq_t *rb_iseq_new_eval (const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth); rb_iseq_t *rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth, - enum rb_iseq_type, const rb_compile_option_t*); + enum rb_iseq_type, const rb_compile_option_t*, + VALUE script_lines); struct iseq_link_anchor; struct rb_iseq_new_with_callback_callback_func { diff --git a/vm_eval.c b/vm_eval.c index d7447bd060a8f5..25fa28d82866dd 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1809,7 +1809,7 @@ eval_make_iseq(VALUE src, VALUE fname, int line, } rb_parser_set_context(parser, parent, FALSE); - rb_parser_set_script_lines(parser, RBOOL(ruby_vm_keep_script_lines)); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); ast = rb_parser_compile_string_path(parser, fname, src, line); if (ast->body.root) { ast->body.coverage_enabled = coverage_enabled; From 07ff4aa19b4f7cda9948ef5104bd1623e0c3eafc Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 15 Apr 2024 13:44:48 +0200 Subject: [PATCH 124/135] Include more debug information in test_uplus_minus --- test/ruby/test_string.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index e395d75e6dc3da..13261eacdd1f79 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3369,7 +3369,7 @@ def test_uplus_minus assert_same(str, -str) bar = -%w(b a r).join('') - assert_same(str, bar, "uminus deduplicates [Feature #13077] #{ObjectSpace.dump(bar)}") + assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}") end def test_uminus_frozen From 43f4da3ebfe39995fa6476af4ba4514ece8e4b4a Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 15 Apr 2024 22:15:52 +0900 Subject: [PATCH 125/135] [ruby/reline] Fix vi_to_column which was broken (https://github.com/ruby/reline/pull/679) https://github.com/ruby/reline/commit/9e93ad52e7 --- lib/reline/line_editor.rb | 19 ++++--------------- test/reline/test_key_actor_vi.rb | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 38df6bd21131e2..45c6894ff882b1 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -540,10 +540,6 @@ def render_differential new_lines.size - y end - def current_row - wrapped_lines.flatten[wrapped_cursor_y] - end - def upper_space_height(wrapped_cursor_y) wrapped_cursor_y - screen_scroll_top end @@ -2483,18 +2479,11 @@ def finish end private def vi_to_column(key, arg: 0) - current_row_width = calculate_width(current_row) - @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc| - # total has [byte_size, cursor] + # Implementing behavior of vi, not Readline's vi-mode. + @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc| mbchar_width = Reline::Unicode.get_mbchar_width(gc) - if (total.last + mbchar_width) >= arg - break total - elsif (total.last + mbchar_width) >= current_row_width - break total - else - total = [total.first + gc.bytesize, total.last + mbchar_width] - total - end + break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg + [total_byte_size + gc.bytesize, total_width + mbchar_width] } end diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index e72eedb904d6d5..cf3943ae379bce 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -711,6 +711,20 @@ def test_ed_move_to_beg assert_line_around_cursor('', ' abcde ABCDE ') end + def test_vi_to_column + input_keys("a一二三\C-[0") + input_keys('1|') + assert_line_around_cursor('', 'a一二三') + input_keys('2|') + assert_line_around_cursor('a', '一二三') + input_keys('3|') + assert_line_around_cursor('a', '一二三') + input_keys('4|') + assert_line_around_cursor('a一', '二三') + input_keys('9|') + assert_line_around_cursor('a一二', '三') + end + def test_vi_delete_meta input_keys("aaa bbb ccc ddd eee\C-[02w") assert_line_around_cursor('aaa bbb ', 'ccc ddd eee') From 0a4e3f23e6f872537faedb8d728a5696f63a7f89 Mon Sep 17 00:00:00 2001 From: Mari Imaizumi Date: Mon, 15 Apr 2024 22:31:33 +0900 Subject: [PATCH 126/135] [ruby/reline] Remove not implemented method (https://github.com/ruby/reline/pull/680) https://github.com/ruby/reline/commit/84762fc588 --- lib/reline/key_actor/emacs.rb | 2 +- lib/reline/line_editor.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index 4c7b8421afb50a..5d0a7fb63d2939 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -55,7 +55,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 26 ^Z :ed_ignore, # 27 ^[ - :em_meta_next, + :ed_unassigned, # 28 ^\ :ed_ignore, # 29 ^] diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 45c6894ff882b1..e4d7ecf1a29189 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -2630,7 +2630,4 @@ def finish @mark_pointer = new_pointer end alias_method :exchange_point_and_mark, :em_exchange_mark - - private def em_meta_next(key) - end end From 2eafed0f3bd33d5a4e6103259e1aba6400e5146e Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 15 Apr 2024 11:59:45 -0400 Subject: [PATCH 127/135] YJIT: A64: Avoid intermediate register in `opt_and` and friends (#10509) Same idea as the x64 equivalent in c2622b52536c5, removing the register shuffle coming from the pop two, push one stack motion these VM instructions perform. ``` # Insn: 0004 opt_or (stack_size: 2) - orr x11, x1, x9 - mov x1, x11 + orr x1, x1, x9 ``` --- yjit/src/backend/arm64/mod.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 092df6326f2e05..a62ea45e7e10ff 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -431,12 +431,33 @@ impl Assembler } } }, - Insn::And { left, right, .. } | - Insn::Or { left, right, .. } | - Insn::Xor { left, right, .. } => { + Insn::And { left, right, out } | + Insn::Or { left, right, out } | + Insn::Xor { left, right, out } => { let (opnd0, opnd1) = split_boolean_operands(asm, *left, *right); *left = opnd0; *right = opnd1; + + // Since these instructions are lowered to an instruction that have 2 input + // registers and an output register, look to merge with an `Insn::Mov` that + // follows which puts the output in another register. For example: + // `Add a, b => out` followed by `Mov c, out` becomes `Add a, b => c`. + if let (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) = (left, right, iterator.peek()) { + if live_ranges[index] == index + 1 { + // Check after potentially lowering a stack operand to a register operand + let lowered_dest = if let Opnd::Stack { .. } = dest { + asm.lower_stack_opnd(dest) + } else { + *dest + }; + if out == src && matches!(lowered_dest, Opnd::Reg(_)) { + *out = lowered_dest; + iterator.map_insn_index(asm); + iterator.next_unmapped(); // Pop merged Insn::Mov + } + } + } + asm.push_insn(insn); }, Insn::CCall { opnds, fptr, .. } => { From d019b3baec4485909e6727db2507f943e78f38ec Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 15 Apr 2024 13:03:26 +0200 Subject: [PATCH 128/135] Emit a performance warning when redefining specially optimized methods This makes it easier to notice a dependency is causing interpreter or JIT deoptimization. ```ruby Warning[:performance] = true class String def freeze super end end ``` ``` ./test.rb:4: warning: Redefining 'String#freeze' disable multiple interpreter and JIT optimizations ``` --- test/ruby/test_optimization.rb | 59 +++++++++++++++++++++++++++++++++- vm.c | 6 ++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 44ec615405d561..a7a0582dbb49c5 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -16,6 +16,18 @@ def #{method}(*args) end; end + def assert_performance_warning(klass, method) + assert_in_out_err([], "#{<<-"begin;"}\n#{<<~"end;"}", [], ["-:4: warning: Redefining '#{klass}##{method}' disables interpreter and JIT optimizations"]) + begin; + Warning[:performance] = true + class #{klass} + undef #{method} + def #{method} + end + end + end; + end + def disasm(name) RubyVM::InstructionSequence.of(method(name)).disasm end @@ -23,102 +35,122 @@ def disasm(name) def test_fixnum_plus assert_equal 21, 10 + 11 assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11') + assert_performance_warning('Integer', '+') end def test_fixnum_minus assert_equal 5, 8 - 3 assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3') + assert_performance_warning('Integer', '-') end def test_fixnum_mul assert_equal 15, 3 * 5 assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5') + assert_performance_warning('Integer', '*') end def test_fixnum_div assert_equal 3, 15 / 5 assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5') + assert_performance_warning('Integer', '/') end def test_fixnum_mod assert_equal 1, 8 % 7 assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7') + assert_performance_warning('Integer', '%') end def test_fixnum_lt assert_equal true, 1 < 2 assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2') + assert_performance_warning('Integer', '<') end def test_fixnum_le assert_equal true, 1 <= 2 assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2') + assert_performance_warning('Integer', '<=') end def test_fixnum_gt assert_equal false, 1 > 2 assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2') + assert_performance_warning('Integer', '>') end def test_fixnum_ge assert_equal false, 1 >= 2 assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2') + assert_performance_warning('Integer', '>=') end def test_float_plus assert_equal 4.0, 2.0 + 2.0 assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + assert_performance_warning('Float', '+') end def test_float_minus assert_equal 4.0, 2.0 + 2.0 - assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + assert_redefine_method('Float', '-', 'assert_equal 2.0, 4.0 - 2.0') + assert_performance_warning('Float', '-') end def test_float_mul assert_equal 29.25, 4.5 * 6.5 assert_redefine_method('Float', '*', 'assert_equal 6.5, 4.5 * 6.5') + assert_performance_warning('Float', '*') end def test_float_div assert_in_delta 0.63063063063063063, 4.2 / 6.66 assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]") + assert_performance_warning('Float', '/') end def test_float_lt assert_equal true, 1.1 < 2.2 assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2') + assert_performance_warning('Float', '<') end def test_float_le assert_equal true, 1.1 <= 2.2 assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2') + assert_performance_warning('Float', '<=') end def test_float_gt assert_equal false, 1.1 > 2.2 assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2') + assert_performance_warning('Float', '>') end def test_float_ge assert_equal false, 1.1 >= 2.2 assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2') + assert_performance_warning('Float', '>=') end def test_string_length assert_equal 6, "string".length assert_redefine_method('String', 'length', 'assert_nil "string".length') + assert_performance_warning('String', 'length') end def test_string_size assert_equal 6, "string".size assert_redefine_method('String', 'size', 'assert_nil "string".size') + assert_performance_warning('String', 'size') end def test_string_empty? assert_equal true, "".empty? assert_equal false, "string".empty? assert_redefine_method('String', 'empty?', 'assert_nil "string".empty?') + assert_performance_warning('String', 'empty?') end def test_string_plus @@ -127,39 +159,50 @@ def test_string_plus assert_equal "x", "" + "x" assert_equal "ab", "a" + "b" assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"') + assert_performance_warning('String', '+') end def test_string_succ assert_equal 'b', 'a'.succ assert_equal 'B', 'A'.succ + assert_performance_warning('String', 'succ') end def test_string_format assert_equal '2', '%d' % 2 assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2') + assert_performance_warning('String', '%') end def test_string_freeze assert_equal "foo", "foo".freeze assert_equal "foo".freeze.object_id, "foo".freeze.object_id assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze') + assert_performance_warning('String', 'freeze') end def test_string_uminus assert_same "foo".freeze, -"foo" assert_redefine_method('String', '-@', 'assert_nil(-"foo")') + assert_performance_warning('String', '-@') end def test_array_min assert_equal 1, [1, 2, 4].min assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)') assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)') + assert_performance_warning('Array', 'min') end def test_array_max assert_equal 4, [1, 2, 4].max assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)') assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)') + assert_performance_warning('Array', 'max') + end + + def test_array_hash + assert_performance_warning('Array', 'hash') end def test_trace_optimized_methods @@ -235,6 +278,8 @@ def test_string_eq_neq assert_equal :b, (b #{m} "b").to_sym end end + + assert_performance_warning('String', '==') end def test_string_ltlt @@ -243,50 +288,59 @@ def test_string_ltlt assert_equal "x", "" << "x" assert_equal "ab", "a" << "b" assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"') + assert_performance_warning('String', '<<') end def test_fixnum_and assert_equal 1, 1&3 assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3') + assert_performance_warning('Integer', '&') end def test_fixnum_or assert_equal 3, 1|3 assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1') + assert_performance_warning('Integer', '|') end def test_array_plus assert_equal [1,2], [1]+[2] assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') + assert_performance_warning('Array', '+') end def test_array_minus assert_equal [2], [1,2] - [1] assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]') + assert_performance_warning('Array', '-') end def test_array_length assert_equal 0, [].length assert_equal 3, [1,2,3].length assert_redefine_method('Array', 'length', 'assert_nil([].length); assert_nil([1,2,3].length)') + assert_performance_warning('Array', 'length') end def test_array_empty? assert_equal true, [].empty? assert_equal false, [1,2,3].empty? assert_redefine_method('Array', 'empty?', 'assert_nil([].empty?); assert_nil([1,2,3].empty?)') + assert_performance_warning('Array', 'empty?') end def test_hash_length assert_equal 0, {}.length assert_equal 1, {1=>1}.length assert_redefine_method('Hash', 'length', 'assert_nil({}.length); assert_nil({1=>1}.length)') + assert_performance_warning('Hash', 'length') end def test_hash_empty? assert_equal true, {}.empty? assert_equal false, {1=>1}.empty? assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)') + assert_performance_warning('Hash', 'empty?') end def test_hash_aref_with @@ -297,6 +351,7 @@ def test_hash_aref_with h = { "foo" => 1 } assert_equal "foo", h["foo"] end; + assert_performance_warning('Hash', '[]') end def test_hash_aset_with @@ -308,6 +363,7 @@ def test_hash_aset_with assert_equal 1, h["foo"] = 1, "assignment always returns value set" assert_nil h["foo"] end; + assert_performance_warning('Hash', '[]=') end class MyObj @@ -639,6 +695,7 @@ def test_eqq [ nil, true, false, 0.1, :sym, 'str', 0xffffffffffffffff ].each do |v| k = v.class.to_s assert_redefine_method(k, '===', "assert_equal(#{v.inspect} === 0, 0)") + assert_performance_warning(k, '===') end end diff --git a/vm.c b/vm.c index e7335aa1bd1e82..3fb57e2eb6e500 100644 --- a/vm.c +++ b/vm.c @@ -2132,6 +2132,12 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass) if (st_lookup(vm_opt_method_def_table, (st_data_t)me->def, &bop)) { int flag = vm_redefinition_check_flag(klass); if (flag != 0) { + rb_category_warn( + RB_WARN_CATEGORY_PERFORMANCE, + "Redefining '%s#%s' disables interpreter and JIT optimizations", + rb_class2name(me->owner), + rb_id2name(me->called_id) + ); rb_yjit_bop_redefined(flag, (enum ruby_basic_operators)bop); rb_rjit_bop_redefined(flag, (enum ruby_basic_operators)bop); ruby_vm_redefined_flag[bop] |= flag; From 66bfcba587df3184d3d32495e2ba70984b36e1b2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Apr 2024 00:46:25 +0900 Subject: [PATCH 129/135] Not all `nm`s support the `--help` option --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 28d006e4368b7d..45d32d87603d1f 100644 --- a/configure.ac +++ b/configure.ac @@ -277,7 +277,7 @@ AC_CHECK_TOOLS([STRIP], [gstrip strip], [:]) # nm errors with Rust's LLVM bitcode when Rust uses a newer LLVM version than nm. # In case we're working with llvm-nm, tell it to not worry about the bitcode. -AS_IF([${NM} --help | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) +AS_IF([${NM} --help 2>&1 | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) AS_IF([test ! $rb_test_CFLAGS], [AS_UNSET(CFLAGS)]); AS_UNSET(rb_test_CFLAGS) AS_IF([test ! $rb_test_CXXFLAGS], [AS_UNSET(CXXFLAGS)]); AS_UNSET(rb_save_CXXFLAGS) From 733d50f0a5744b87da09e274b23cb0519206b88e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Apr 2024 15:56:43 -0400 Subject: [PATCH 130/135] Remove dependency on gc.h for darray.h --- darray.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/darray.h b/darray.h index 41d386e4563347..d24e3c3eb5f5c3 100644 --- a/darray.h +++ b/darray.h @@ -6,7 +6,6 @@ #include #include "internal/bits.h" -#include "internal/gc.h" // Type for a dynamic array. Use to declare a dynamic array. // It is a pointer so it fits in st_table nicely. Designed @@ -125,8 +124,7 @@ rb_darray_capa(const void *ary) static inline void rb_darray_free(void *ary) { - rb_darray_meta_t *meta = ary; - if (meta) ruby_sized_xfree(ary, meta->capa); + xfree(ary); } /* Internal function. Resizes the capacity of a darray. The new capacity must @@ -137,7 +135,7 @@ rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; - rb_darray_meta_t *new_ary = rb_xrealloc_mul_add(meta, new_capa, element_size, header_size); + rb_darray_meta_t *new_ary = xrealloc(meta, new_capa * element_size + header_size); if (meta == NULL) { /* First allocation. Initialize size. On subsequence allocations @@ -180,7 +178,7 @@ rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, siz return; } - rb_darray_meta_t *meta = rb_xcalloc_mul_add(array_size, element_size, header_size); + rb_darray_meta_t *meta = xcalloc(array_size * element_size + header_size, 1); meta->size = array_size; meta->capa = array_size; From 1984f9aedcbb11f0770257eb5ecd4d4f37e0efd5 Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Mon, 15 Apr 2024 10:55:14 -0700 Subject: [PATCH 131/135] Specify Kernel#autoload? uses current namespace Because Kernel#autoload? uses the current namespace, it can lead to potentially confusing results. We should make it clearer that modules count as separate namespaces to lookup in. Co-authored-by: Jeremy Evans Co-authored-by: Nobuyoshi Nakada --- load.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/load.c b/load.c index a9c08c62a8de94..dcf50f5b7a91c8 100644 --- a/load.c +++ b/load.c @@ -1532,10 +1532,22 @@ rb_f_autoload(VALUE obj, VALUE sym, VALUE file) * autoload?(name, inherit=true) -> String or nil * * Returns _filename_ to be loaded if _name_ is registered as - * +autoload+. + * +autoload+ in the current namespace or one of its ancestors. * * autoload(:B, "b") * autoload?(:B) #=> "b" + * + * module C + * autoload(:D, "d") + * autoload?(:D) #=> "d" + * autoload?(:B) #=> nil + * end + * + * class E + * autoload(:F, "f") + * autoload?(:F) #=> "f" + * autoload?(:B) #=> "b" + * end */ static VALUE From f86fb1eda23ee3d59dc59452d0d7078002845d12 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 15 Apr 2024 11:01:01 -0700 Subject: [PATCH 132/135] add allocation benchmark --- benchmark/object_allocate.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/benchmark/object_allocate.yml b/benchmark/object_allocate.yml index 93ff463e415585..bdbd4536db2e6a 100644 --- a/benchmark/object_allocate.yml +++ b/benchmark/object_allocate.yml @@ -11,6 +11,26 @@ prelude: | class OneTwentyEight 128.times { include(Module.new) } end + class OnePositional + def initialize a; end + end + class TwoPositional + def initialize a, b; end + end + class ThreePositional + def initialize a, b, c; end + end + class FourPositional + def initialize a, b, c, d; end + end + class KWArg + def initialize a:, b:, c:, d: + end + end + class Mixed + def initialize a, b, c:, d: + end + end # Disable GC to see raw throughput: GC.disable benchmark: @@ -18,4 +38,11 @@ benchmark: allocate_32_deep: ThirtyTwo.new allocate_64_deep: SixtyFour.new allocate_128_deep: OneTwentyEight.new + allocate_1_positional_params: OnePositional.new(1) + allocate_2_positional_params: TwoPositional.new(1, 2) + allocate_3_positional_params: ThreePositional.new(1, 2, 3) + allocate_4_positional_params: FourPositional.new(1, 2, 3, 4) + allocate_kwarg_params: "KWArg.new(a: 1, b: 2, c: 3, d: 4)" + allocate_mixed_params: "Mixed.new(1, 2, c: 3, d: 4)" + allocate_no_params: "Object.new" loop_count: 100000 From a2ea4ec30c409ebe8ae7952f092e0bd8c1912a17 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Tue, 2 Apr 2024 11:28:52 +0100 Subject: [PATCH 133/135] Add --with-shared-gc build flag --- configure.ac | 3 +++ tool/m4/ruby_shared_gc.m4 | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tool/m4/ruby_shared_gc.m4 diff --git a/configure.ac b/configure.ac index 45d32d87603d1f..9ea001385007d3 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,7 @@ m4_include([tool/m4/ruby_replace_type.m4])dnl m4_include([tool/m4/ruby_require_funcs.m4])dnl m4_include([tool/m4/ruby_rm_recursive.m4])dnl m4_include([tool/m4/ruby_setjmp_type.m4])dnl +m4_include([tool/m4/ruby_shared_gc.m4])dnl m4_include([tool/m4/ruby_stack_grow_direction.m4])dnl m4_include([tool/m4/ruby_thread.m4])dnl m4_include([tool/m4/ruby_try_cflags.m4])dnl @@ -3750,6 +3751,7 @@ AS_IF([test x"$gcov" = xyes], [ ]) RUBY_SETJMP_TYPE +RUBY_SHARED_GC } [begin]_group "installation section" && { @@ -4656,6 +4658,7 @@ config_summary "target OS" "$target_os" config_summary "compiler" "$CC" config_summary "with thread" "$THREAD_MODEL" config_summary "with coroutine" "$coroutine_type" +config_summary "with shared GC" "$with_shared_gc" config_summary "enable shared libs" "$ENABLE_SHARED" config_summary "dynamic library ext" "$DLEXT" config_summary "CFLAGS" "$cflags" diff --git a/tool/m4/ruby_shared_gc.m4 b/tool/m4/ruby_shared_gc.m4 new file mode 100644 index 00000000000000..a27b9b8505ca77 --- /dev/null +++ b/tool/m4/ruby_shared_gc.m4 @@ -0,0 +1,19 @@ +dnl -*- Autoconf -*- +AC_DEFUN([RUBY_SHARED_GC],[ +AC_ARG_WITH(shared-gc, + AS_HELP_STRING([--with-shared-gc], + [Enable replacement of Ruby's GC from a shared library.]), + [with_shared_gc=$withval], [unset with_shared_gc] +) + +AC_SUBST([with_shared_gc]) +AC_MSG_CHECKING([if Ruby is build with shared GC support]) +AS_IF([test "$with_shared_gc" = "yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([USE_SHARED_GC], [1]) +], [ + AC_MSG_RESULT([no]) + with_shared_gc="no" + AC_DEFINE([USE_SHARED_GC], [0]) +]) +])dnl From 065710c0f5c1e81f0fa7ab6ddd3ccbc41e9cc2cc Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Tue, 2 Apr 2024 13:52:50 +0100 Subject: [PATCH 134/135] Initialize external GC Library Co-Authored-By: Peter Zhu --- dln.c | 4 ++-- dln.h | 1 + dmydln.c | 10 ++++++++++ gc.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++-- internal/gc.h | 4 ++++ vm_core.h | 11 +++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) diff --git a/dln.c b/dln.c index db6ea5aa0fb03d..3e08728a6beb02 100644 --- a/dln.c +++ b/dln.c @@ -339,7 +339,7 @@ dln_disable_dlclose(void) #endif #if defined(_WIN32) || defined(USE_DLN_DLOPEN) -static void * +void * dln_open(const char *file) { static const char incompatible[] = "incompatible library version"; @@ -427,7 +427,7 @@ dln_open(const char *file) dln_loaderror("%s - %s", error, file); } -static void * +void * dln_sym(void *handle, const char *symbol) { #if defined(_WIN32) diff --git a/dln.h b/dln.h index d624bb6611d207..13eef58d9fbc88 100644 --- a/dln.h +++ b/dln.h @@ -25,6 +25,7 @@ RUBY_SYMBOL_EXPORT_BEGIN char *dln_find_exe_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); char *dln_find_file_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); void *dln_load(const char*); +void *dln_open(const char *file); void *dln_symbol(void*,const char*); RUBY_SYMBOL_EXPORT_END diff --git a/dmydln.c b/dmydln.c index 35824ebec8b832..22f40e82eb886f 100644 --- a/dmydln.c +++ b/dmydln.c @@ -20,3 +20,13 @@ dln_symbol(void *handle, const char *symbol) UNREACHABLE_RETURN(NULL); } + +NORETURN(void *dln_open(const char*)); +void* +dln_open(const char *library) +{ + rb_loaderror("this executable file can't load extension libraries"); + + UNREACHABLE_RETURN(NULL); +} + diff --git a/gc.c b/gc.c index 9be07febc8258d..a08a151efa6699 100644 --- a/gc.c +++ b/gc.c @@ -1884,6 +1884,55 @@ rb_gc_initial_stress_set(VALUE flag) initial_stress = flag; } +static void * Alloc_GC_impl(void); + +#if USE_SHARED_GC +# include "dln.h" +# define Alloc_GC rb_gc_functions->init + +void +ruby_external_gc_init() +{ + rb_gc_function_map_t *map = malloc(sizeof(rb_gc_function_map_t)); + rb_gc_functions = map; + + char *gc_so_path = getenv("RUBY_GC_LIBRARY_PATH"); + if (!gc_so_path) { + map->init = Alloc_GC_impl; + return; + } + + void *h = dln_open(gc_so_path); + if (!h) { + rb_bug( + "ruby_external_gc_init: Shared library %s cannot be opened.", + gc_so_path + ); + } + + void *gc_init_func = dln_symbol(h, "Init_GC"); + if (!gc_init_func) { + rb_bug( + "ruby_external_gc_init: Init_GC func not exported by library %s", + gc_so_path + ); + } + + map->init = gc_init_func; +} +#else +# define Alloc_GC Alloc_GC_impl +#endif + +rb_objspace_t * +rb_objspace_alloc(void) +{ +#if USE_SHARED_GC + ruby_external_gc_init(); +#endif + return (rb_objspace_t *)Alloc_GC(); +} + static void free_stack_chunks(mark_stack_t *); static void mark_stack_free_cache(mark_stack_t *); static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page); @@ -3489,8 +3538,8 @@ static const struct st_hash_type object_id_hash_type = { object_id_hash, }; -rb_objspace_t * -rb_objspace_alloc(void) +static void * +Alloc_GC_impl(void) { rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); ruby_current_vm_ptr->objspace = objspace; diff --git a/internal/gc.h b/internal/gc.h index 5b1180fd91ccdc..91dbc0ccf37e74 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -16,6 +16,10 @@ #include "ruby/ruby.h" /* for rb_event_flag_t */ #include "vm_core.h" /* for GET_EC() */ +#ifndef USE_SHARED_GC +# define USE_SHARED_GC 0 +#endif + #if defined(__x86_64__) && !defined(_ILP32) && defined(__GNUC__) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("movq\t%%rsp, %0" : "=r" (*(p))) #elif defined(__i386) && defined(__GNUC__) diff --git a/vm_core.h b/vm_core.h index 9873ada2d57197..9d6f8d87d0c682 100644 --- a/vm_core.h +++ b/vm_core.h @@ -106,6 +106,14 @@ extern int ruby_assert_critical_section_entered; #include "ruby/thread_native.h" +#if USE_SHARED_GC +typedef struct gc_function_map { + void *(*init)(void); +} rb_gc_function_map_t; + +#define rb_gc_functions (GET_VM()->gc_functions_map) +#endif + /* * implementation selector of get_insn_info algorithm * 0: linear search @@ -752,6 +760,9 @@ typedef struct rb_vm_struct { int coverage_mode; struct rb_objspace *objspace; +#if USE_SHARED_GC + rb_gc_function_map_t *gc_functions_map; +#endif rb_at_exit_list *at_exit; From f5d89267c46ea86c34b9e174e999fea15d48a8e1 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 4 Apr 2024 19:48:41 +0100 Subject: [PATCH 135/135] Add --with-shared-gc to Ubuntu CI --- .github/workflows/ubuntu.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 419684e7f312ce..42b485700d3838 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -33,6 +33,8 @@ jobs: configure: '--disable-yjit' - test_task: check configure: '--enable-shared --enable-load-relative' + - test_task: check + configure: '--with-shared-gc' - test_task: test-bundler-parallel - test_task: test-bundled-gems - test_task: check