From 300dee1fe8275b7444007b418323544b571f585c Mon Sep 17 00:00:00 2001 From: Nuno Silva Date: Tue, 6 Feb 2024 16:46:41 +0000 Subject: [PATCH 001/142] [ruby/irb] Fix usage of tracer gem and add tests (https://github.com/ruby/irb/pull/857) The new tests are skipped when ruby below 3.1, as it was a default gem on it, and in a version we do not support. This also move definition of `use_tracer` to module Context instead of monkey patch. https://github.com/ruby/irb/commit/08834fbd5f --- lib/irb/context.rb | 5 ++ lib/irb/ext/tracer.rb | 60 +++--------------------- lib/irb/extend-command.rb | 1 - test/irb/test_context.rb | 2 +- test/irb/test_init.rb | 6 +++ test/irb/test_tracer.rb | 97 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 55 deletions(-) create mode 100644 test/irb/test_tracer.rb diff --git a/lib/irb/context.rb b/lib/irb/context.rb index ac61b765c0f3a9..5b8791c3ba7e05 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -161,6 +161,11 @@ def initialize(irb, workspace = nil, input_method = nil) private_constant :KEYWORD_ALIASES + def use_tracer=(val) + require_relative "ext/tracer" + @use_tracer = val + end + private def build_completor completor_type = IRB.conf[:COMPLETOR] case completor_type diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index 3eaeb70ef27dd2..53b2e62245a6b5 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -3,76 +3,30 @@ # irb/lib/tracer.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) # - +# Loading the gem "tracer" will cause it to extend IRB commands with: +# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb begin require "tracer" rescue LoadError $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." - module IRB - class Context - def use_tracer=(opt) - # do nothing - end - end - end return # This is about to disable loading below end module IRB - - # initialize tracing function - def IRB.initialize_tracer - Tracer.verbose = false - Tracer.add_filter { - |event, file, line, id, binding, *rests| - /^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and - File::basename(file) != "irb.rb" - } - end - - class Context - # Whether Tracer is used when evaluating statements in this context. - # - # See +lib/tracer.rb+ for more information. - attr_reader :use_tracer - alias use_tracer? use_tracer - - # Sets whether or not to use the Tracer library when evaluating statements - # in this context. - # - # See +lib/tracer.rb+ for more information. - def use_tracer=(opt) - if opt - Tracer.set_get_line_procs(@irb_path) { - |line_no, *rests| - @io.line(line_no) - } - elsif !opt && @use_tracer - Tracer.off - end - @use_tracer=opt - end - end - class WorkSpace alias __evaluate__ evaluate # Evaluate the context of this workspace and use the Tracer library to # output the exact lines of code are being executed in chronological order. # - # See +lib/tracer.rb+ for more information. - def evaluate(context, statements, file = nil, line = nil) - if context.use_tracer? && file != nil && line != nil - Tracer.on - begin + # See https://github.com/ruby/tracer for more information. + def evaluate(statements, file = __FILE__, line = __LINE__) + if IRB.conf[:USE_TRACER] == true + CallTracer.new(colorize: Color.colorable?).start do __evaluate__(statements, file, line) - ensure - Tracer.off end else - __evaluate__(statements, file || __FILE__, line || __LINE__) + __evaluate__(statements, file, line) end end end - - IRB.initialize_tracer end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 69d83080df8b04..91ca96e91a741a 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -317,7 +317,6 @@ module ContextExtender @EXTEND_COMMANDS = [ [:eval_history=, "ext/eval_history.rb"], - [:use_tracer=, "ext/tracer.rb"], [:use_loader=, "ext/use-loader.rb"], ] diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index dad819b4c168e1..a761521691f204 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'tempfile' require 'irb' -require 'rubygems' if defined?(Gem) +require 'rubygems' require_relative "helper" diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 03650981329a0f..64bd96d022a83a 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -216,6 +216,12 @@ def test_dash assert_equal(['-f'], argv) end + def test_option_tracer + argv = %w[--tracer] + IRB.setup(eval("__FILE__"), argv: argv) + assert_equal(true, IRB.conf[:USE_TRACER]) + end + private def with_argv(argv) diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb new file mode 100644 index 00000000000000..f8da066b6351c0 --- /dev/null +++ b/test/irb/test_tracer.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false +require 'tempfile' +require 'irb' +require 'rubygems' + +require_relative "helper" + +module TestIRB + class ContextWithTracerIntegrationTest < IntegrationTestCase + def setup + super + + @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') + end + + def example_ruby_file + <<~'RUBY' + class Foo + def self.foo + 100 + end + end + + def bar(obj) + obj.foo + end + + binding.irb + RUBY + end + + def test_use_tracer_is_disabled_by_default + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = false + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_nil IRB.conf[:USER_TRACER] + assert_not_include(output, "#depth:") + assert_not_include(output, "Foo.foo") + end + + def test_use_tracer_enabled_when_gem_is_unavailable + begin + gem 'tracer' + omit "Skipping because 'tracer' gem is available." + rescue Gem::LoadError + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") + end + end + + def test_use_tracer_enabled_when_gem_is_available + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0') + omit "Ruby version before 3.1.0 does not support Tracer integration. Skipping this test." + end + + begin + gem 'tracer' + rescue Gem::LoadError + omit "Skipping because 'tracer' gem is not available. Enable with WITH_TRACER=true." + end + + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Object#bar at") + assert_include(output, "Foo.foo at") + assert_include(output, "Foo.foo #=> 100") + assert_include(output, "Object#bar #=> 100") + end + end +end From d77172b79b1cd28bc38a75162e41d2c8b6e71088 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 11:15:33 -0500 Subject: [PATCH 002/142] [PRISM] Fix deconstruct index for posts --- prism_compile.c | 69 ++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 2e39d4614542b2..c529c4d88cc9ea 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6673,14 +6673,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // those anonymous items temporary names (as below) int local_index = 0; - // We will assign these values now, if applicable, and use them for - // the ISEQs on these multis - int post_multis_hidden_index = 0; - // Here we figure out local table indices and insert them in to the // index lookup table and local tables. // - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^^^^^^^^^ if (requireds_list && requireds_list->size) { for (size_t i = 0; i < requireds_list->size; i++, local_index++) { @@ -6692,14 +6688,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_node_t *required = requireds_list->nodes[i]; switch (PM_NODE_TYPE(required)) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^^^^^^ case PM_MULTI_TARGET_NODE: { local = rb_make_temporary_id(local_index); local_table_for_iseq->ids[local_index] = local; break; } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^ case PM_REQUIRED_PARAMETER_NODE: { pm_required_parameter_node_t * param = (pm_required_parameter_node_t *)required; @@ -6724,7 +6720,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, body->param.flags.has_lead = true; } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^ if (optionals_list && optionals_list->size) { body->param.opt_num = (int) optionals_list->size; @@ -6744,7 +6740,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (parameters_node && parameters_node->rest) { body->param.rest_start = local_index; @@ -6758,7 +6754,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_constant_id_t name = ((pm_rest_parameter_node_t *) parameters_node->rest)->name; if (name) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (PM_NODE_FLAG_P(parameters_node->rest, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { ID local = pm_constant_id_lookup(scope_node, name); @@ -6769,7 +6765,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } else { - // def foo(a, (b, *c, d), e = 1, *, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *, g, (h, *i, j), k:, l: 1, **m, &n) // ^ pm_insert_local_special(idMULT, local_index, index_lookup_table, local_table_for_iseq); } @@ -6778,7 +6774,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^^^^^^^^^ if (posts_list && posts_list->size) { body->param.post_num = (int) posts_list->size; @@ -6787,23 +6783,24 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, for (size_t i = 0; i < posts_list->size; i++, local_index++) { ID local; - // For each MultiTargetNode, we're going to have one - // additional anonymous local not represented in the locals table - // We want to account for this in our table size - pm_node_t *post_node = posts_list->nodes[i]; + + // For each MultiTargetNode, we're going to have one additional + // anonymous local not represented in the locals table. We want + // to account for this in our table size. + const pm_node_t *post_node = posts_list->nodes[i]; + switch (PM_NODE_TYPE(post_node)) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^^^^^^ case PM_MULTI_TARGET_NODE: { - post_multis_hidden_index = local_index; local = rb_make_temporary_id(local_index); local_table_for_iseq->ids[local_index] = local; break; } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^ case PM_REQUIRED_PARAMETER_NODE: { - pm_required_parameter_node_t * param = (pm_required_parameter_node_t *)post_node; + const pm_required_parameter_node_t *param = (const pm_required_parameter_node_t *) post_node; if (PM_NODE_FLAG_P(param, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { ID local = pm_constant_id_lookup(scope_node, param->name); @@ -6821,7 +6818,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^^^^^ // Keywords create an internal variable on the parse tree if (keywords_list && keywords_list->size) { @@ -6840,7 +6837,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_node_t *keyword_parameter_node = keywords_list->nodes[i]; pm_constant_id_t name; - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (PM_NODE_TYPE_P(keyword_parameter_node, PM_REQUIRED_KEYWORD_PARAMETER_NODE)) { name = ((pm_required_keyword_parameter_node_t *)keyword_parameter_node)->name; @@ -6862,7 +6859,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_node_t *keyword_parameter_node = keywords_list->nodes[i]; pm_constant_id_t name; - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^ if (PM_NODE_TYPE_P(keyword_parameter_node, PM_OPTIONAL_KEYWORD_PARAMETER_NODE)) { pm_optional_keyword_parameter_node_t *cast = ((pm_optional_keyword_parameter_node_t *)keyword_parameter_node); @@ -6921,20 +6918,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } if (parameters_node) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^ if (parameters_node->keyword_rest) { switch (PM_NODE_TYPE(parameters_node->keyword_rest)) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **nil, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **nil, &n) // ^^^^^ case PM_NO_KEYWORDS_PARAMETER_NODE: { body->param.flags.accepts_no_kwarg = true; break; } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^ case PM_KEYWORD_REST_PARAMETER_NODE: { - pm_keyword_rest_parameter_node_t *kw_rest_node = (pm_keyword_rest_parameter_node_t *)parameters_node->keyword_rest; + const pm_keyword_rest_parameter_node_t *kw_rest_node = (const pm_keyword_rest_parameter_node_t *) parameters_node->keyword_rest; if (!body->param.flags.has_kw) { body->param.keyword = keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1); } @@ -6994,7 +6991,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (parameters_node->block) { body->param.block_start = local_index; @@ -7036,9 +7033,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // For each MultiTargetNode, we're going to have one // additional anonymous local not represented in the locals table // We want to account for this in our table size - pm_node_t *required = requireds_list->nodes[i]; + const pm_node_t *required = requireds_list->nodes[i]; + if (PM_NODE_TYPE_P(required, PM_MULTI_TARGET_NODE)) { - local_index = pm_compile_destructured_param_locals((pm_multi_target_node_t *)required, index_lookup_table, local_table_for_iseq, scope_node, local_index); + local_index = pm_compile_destructured_param_locals((const pm_multi_target_node_t *) required, index_lookup_table, local_table_for_iseq, scope_node, local_index); } } } @@ -7049,9 +7047,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // For each MultiTargetNode, we're going to have one // additional anonymous local not represented in the locals table // We want to account for this in our table size - pm_node_t *post= posts_list->nodes[i]; + const pm_node_t *post = posts_list->nodes[i]; + if (PM_NODE_TYPE_P(post, PM_MULTI_TARGET_NODE)) { - local_index = pm_compile_destructured_param_locals((pm_multi_target_node_t *)post, index_lookup_table, local_table_for_iseq, scope_node, local_index); + local_index = pm_compile_destructured_param_locals((const pm_multi_target_node_t *) post, index_lookup_table, local_table_for_iseq, scope_node, local_index); } } } @@ -7160,7 +7159,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_constant_id_t name; switch (PM_NODE_TYPE(keyword_parameter_node)) { - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^ case PM_OPTIONAL_KEYWORD_PARAMETER_NODE: { pm_optional_keyword_parameter_node_t *cast = ((pm_optional_keyword_parameter_node_t *)keyword_parameter_node); @@ -7186,7 +7185,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, optional_index++; break; } - // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) + // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ case PM_REQUIRED_KEYWORD_PARAMETER_NODE: { break; @@ -7220,7 +7219,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_node_t *post = posts_list->nodes[i]; if (PM_NODE_TYPE_P(post, PM_MULTI_TARGET_NODE)) { - ADD_GETLOCAL(ret, &dummy_line_node, table_size - post_multis_hidden_index, 0); + ADD_GETLOCAL(ret, &dummy_line_node, table_size - body->param.post_start - (int) i, 0); pm_compile_destructured_param_writes(iseq, (const pm_multi_target_node_t *) post, ret, scope_node); } } From ccec209b2cced2ddb8463c4933ef729a44d0363c Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 11:59:49 -0500 Subject: [PATCH 003/142] [PRISM] Fix fsl coming from file --- iseq.c | 9 ++++++++- prism_compile.c | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/iseq.c b/iseq.c index e2e9b4d6e7e937..440270bd2957bc 100644 --- a/iseq.c +++ b/iseq.c @@ -1002,8 +1002,15 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa .end_pos = { .lineno = (int) end.line, .column = (int) end.column } }; + rb_compile_option_t *current_option = (rb_compile_option_t *) option; + if (node->parser->frozen_string_literal) { + rb_compile_option_t new_option = *option; + new_option.frozen_string_literal = true; + current_option = &new_option; + } + prepare_iseq_build(iseq, name, path, realpath, first_lineno, &code_location, -1, - parent, isolated_depth, type, Qnil, option); + parent, isolated_depth, type, Qnil, current_option); pm_iseq_compile_node(iseq, node); finish_iseq_build(iseq); diff --git a/prism_compile.c b/prism_compile.c index c529c4d88cc9ea..2e1666524fd90b 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -773,12 +773,13 @@ pm_interpolated_node_compile(pm_node_list_t *parts, rb_iseq_t *iseq, NODE dummy_ current_string = rb_enc_str_new(NULL, 0, enc); } - if (parser->frozen_string_literal) { + if (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { ADD_INSN1(ret, &dummy_line_node, putobject, rb_str_freeze(current_string)); } else { ADD_INSN1(ret, &dummy_line_node, putstring, rb_str_freeze(current_string)); } + current_string = Qnil; number_of_items_pushed++; @@ -793,12 +794,14 @@ pm_interpolated_node_compile(pm_node_list_t *parts, rb_iseq_t *iseq, NODE dummy_ if (RTEST(current_string)) { current_string = rb_fstring(current_string); - if (parser->frozen_string_literal) { + + if (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { ADD_INSN1(ret, &dummy_line_node, putobject, current_string); } else { ADD_INSN1(ret, &dummy_line_node, putstring, current_string); } + current_string = Qnil; number_of_items_pushed++; } From f5b368df0ceb1e705cd94e39ef8459dae07e6d52 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 12:59:47 -0500 Subject: [PATCH 004/142] [ruby/prism] Better invalid token messages https://github.com/ruby/prism/commit/8c9bed2a4d --- prism/diagnostic.c | 4 +++- prism/diagnostic.h | 4 +++- prism/prism.c | 18 ++++++++++++++---- test/prism/format_errors_test.rb | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/prism/diagnostic.c b/prism/diagnostic.c index df7ae381ba6ac5..c718246c80cf42 100644 --- a/prism/diagnostic.c +++ b/prism/diagnostic.c @@ -198,8 +198,10 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_NUMBER_UNDERSCORE] = { "invalid underscore placement in number", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INVALID_CHARACTER] = { "invalid character 0x%X", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT? - [PM_ERR_INVALID_TOKEN] = { "invalid token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT? [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "invalid global variable", PM_ERROR_LEVEL_FATAL }, [PM_ERR_IT_NOT_ALLOWED] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, [PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_FATAL }, diff --git a/prism/diagnostic.h b/prism/diagnostic.h index 35a5c88793ed4c..019afb96b32f1c 100644 --- a/prism/diagnostic.h +++ b/prism/diagnostic.h @@ -196,8 +196,10 @@ typedef enum { PM_ERR_INVALID_NUMBER_HEXADECIMAL, PM_ERR_INVALID_NUMBER_OCTAL, PM_ERR_INVALID_NUMBER_UNDERSCORE, + PM_ERR_INVALID_CHARACTER, + PM_ERR_INVALID_MULTIBYTE_CHARACTER, + PM_ERR_INVALID_PRINTABLE_CHARACTER, PM_ERR_INVALID_PERCENT, - PM_ERR_INVALID_TOKEN, PM_ERR_INVALID_VARIABLE_GLOBAL, PM_ERR_IT_NOT_ALLOWED, PM_ERR_LAMBDA_OPEN, diff --git a/prism/prism.c b/prism/prism.c index 3ed55f06d8c600..22503fd726f6e7 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -9590,11 +9590,21 @@ parser_lex(pm_parser_t *parser) { if (*parser->current.start != '_') { size_t width = char_is_identifier_start(parser, parser->current.start); - // If this isn't the beginning of an identifier, then it's an invalid - // token as we've exhausted all of the other options. We'll skip past - // it and return the next token. + // If this isn't the beginning of an identifier, then + // it's an invalid token as we've exhausted all of the + // other options. We'll skip past it and return the next + // token after adding an appropriate error message. if (!width) { - pm_parser_err_current(parser, PM_ERR_INVALID_TOKEN); + pm_diagnostic_id_t diag_id; + if (*parser->current.start >= 0x80) { + diag_id = PM_ERR_INVALID_MULTIBYTE_CHARACTER; + } else if (char_is_ascii_printable(*parser->current.start) || (*parser->current.start == '\\')) { + diag_id = PM_ERR_INVALID_PRINTABLE_CHARACTER; + } else { + diag_id = PM_ERR_INVALID_CHARACTER; + } + + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, *parser->current.start); goto lex_next_token; } diff --git a/test/prism/format_errors_test.rb b/test/prism/format_errors_test.rb index bc0b26165d2bdb..a142e8eee15d3a 100644 --- a/test/prism/format_errors_test.rb +++ b/test/prism/format_errors_test.rb @@ -16,7 +16,7 @@ def test_format_errors assert_equal <<~'ERROR', Debug.format_errors('"%W"\u"', false) > 1 | "%W"\u" | ^ expected a newline or semicolon after the statement - | ^ invalid token + | ^ invalid character `\` | ^ expected a closing delimiter for the string literal ERROR end From c1bc7147200b5e47816388f1d304454f25e50bcb Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 13:53:58 -0500 Subject: [PATCH 005/142] [PRISM] Do not show source snippets if non-UTF-8 --- prism_compile.c | 96 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 2e1666524fd90b..a2b9f7a1f0ba66 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -7633,6 +7633,71 @@ pm_parse_result_free(pm_parse_result_t *result) pm_options_free(&result->options); } +/** + * Check if the given source slice is valid UTF-8. + */ +static bool +pm_parse_input_error_utf8_p(const uint8_t *start, const uint8_t *end) +{ + size_t width; + + while (start < end) { + if ((width = pm_encoding_utf_8_char_width(start, end - start)) == 0) return false; + start += width; + } + + return true; +} + +/** + * Generate an error object from the given parser that contains as much + * information as possible about the errors that were encountered. + */ +static VALUE +pm_parse_input_error(const pm_parse_result_t *result) +{ + const pm_diagnostic_t *head = (const pm_diagnostic_t *) result->parser.error_list.head; + bool valid_utf8 = true; + + for (const pm_diagnostic_t *error = head; error != NULL; error = (const pm_diagnostic_t *) error->node.next) { + // Any errors with the level PM_ERROR_LEVEL_ARGUMENT effectively take + // over as the only argument that gets raised. This is to allow priority + // messages that should be handled before anything else. + if (error->level == PM_ERROR_LEVEL_ARGUMENT) { + return rb_exc_new(rb_eArgError, error->message, strlen(error->message)); + } + + // It is implicitly assumed that the error messages will be encodeable + // as UTF-8. Because of this, we can't include source examples that + // contain invalid byte sequences. So if any source examples include + // invalid UTF-8 byte sequences, we will skip showing source examples + // entirely. + if (valid_utf8 && !pm_parse_input_error_utf8_p(error->location.start, error->location.end)) { + valid_utf8 = false; + } + } + + pm_buffer_t buffer = { 0 }; + pm_buffer_append_string(&buffer, "syntax errors found\n", 20); + + if (valid_utf8) { + pm_parser_errors_format(&result->parser, &buffer, rb_stderr_tty_p()); + } + else { + const pm_string_t *filepath = &result->parser.filepath; + + for (const pm_diagnostic_t *error = head; error != NULL; error = (pm_diagnostic_t *) error->node.next) { + if (error != head) pm_buffer_append_byte(&buffer, '\n'); + pm_buffer_append_format(&buffer, "%.*s:%" PRIu32 ": %s", (int) pm_string_length(filepath), pm_string_source(filepath), (uint32_t) pm_newline_list_line_column(&result->parser.newline_list, error->location.start).line, error->message); + } + } + + VALUE error = rb_exc_new(rb_eSyntaxError, pm_buffer_value(&buffer), pm_buffer_length(&buffer)); + pm_buffer_free(&buffer); + + return error; +} + /** * Parse the parse result and raise a Ruby error if there are any syntax errors. * It returns an error if one should be raised. It is assumed that the parse @@ -7648,38 +7713,11 @@ pm_parse_input(pm_parse_result_t *result, VALUE filepath) // If there are errors, raise an appropriate error and free the result. if (result->parser.error_list.size > 0) { - // Determine the error to raise. Any errors with the level - // PM_ERROR_LEVEL_ARGUMENT effectively take over as the only argument - // that gets raised. - const pm_diagnostic_t *error_argument = NULL; - for (const pm_diagnostic_t *error = (pm_diagnostic_t *) result->parser.error_list.head; error != NULL; error = (pm_diagnostic_t *) error->node.next) { - if (error->level == PM_ERROR_LEVEL_ARGUMENT) { - error_argument = error; - break; - } - } - - VALUE error_class; - pm_buffer_t buffer = { 0 }; - - // If we found an error argument, then we need to use that as the error - // message. Otherwise, we will format all of the parser error together. - if (error_argument == NULL) { - error_class = rb_eSyntaxError; - pm_buffer_append_string(&buffer, "syntax errors found\n", 20); - pm_parser_errors_format(&result->parser, &buffer, rb_stderr_tty_p()); - } - else { - error_class = rb_eArgError; - pm_buffer_append_string(&buffer, error_argument->message, strlen(error_argument->message)); - } - - VALUE error_object = rb_exc_new(error_class, pm_buffer_value(&buffer), pm_buffer_length(&buffer)); - pm_buffer_free(&buffer); + VALUE error = pm_parse_input_error(result); // TODO: We need to set the backtrace. // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); - return error_object; + return error; } // Emit all of the various warnings from the parse. From c3403322df7d81cd9426310bd7833d005b2a7ac7 Mon Sep 17 00:00:00 2001 From: Nikita Vasilevsky Date: Tue, 6 Feb 2024 18:14:27 +0000 Subject: [PATCH 006/142] [PRISM] Use block node location when building block iseq Co-Authored-By: Kevin Newton --- prism_compile.c | 5 ++++- test/ruby/test_proc.rb | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index a2b9f7a1f0ba66..98ee84ea10422a 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -2939,7 +2939,10 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c // Scope associated with the block pm_scope_node_t next_scope_node; pm_scope_node_init(call_node->block, &next_scope_node, scope_node, parser); - block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + + int block_lineno = (int) pm_newline_list_line_column(&newline_list, call_node->block->location.start).line; + block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, block_lineno); + pm_scope_node_destroy(&next_scope_node); if (ISEQ_BODY(block_iseq)->catch_table) { diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 8d6ebf5dcbb39c..2f91da8aa8f341 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1846,4 +1846,3 @@ def g.>>(f) to_proc >> f end assert_raise(ArgumentError) { (f >> g).call(**{})[:a] } end end - From 936c0ab5e807e7060e6fc65a3c45015c131ae873 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Mon, 5 Feb 2024 14:39:29 -0500 Subject: [PATCH 007/142] [ruby/prism] Implement file parsing error handling This PR implements proper file parsing error handling. Previously `file_options` would call `pm_string_mapped_init` which would print an error from `perror`. However this wouldn't raise a proper Ruby error so it was just a string output. I've done the following: - Raise an error from `rb_syserr_fail` with the filepath in `file_options`. - No longer return `Qnil` if `file_options` returns false (because now it will raise) - Update `file_options` to return `static void` instead of `static bool`. - Update `file_options` and `profile_file` to check the type so when passing `nil` we see a `TypeError`. - Delete `perror` from `pm_string_mapped_init` - Update `FFI` backend to raise appropriate errors when calling `pm_string_mapped_init`. - Add tests for `dump_file`, `lex_file`, `parse_file`, `parse_file_comments`, `parse_lex_file`, and `parse_file_success?` when a file doesn't exist and for `nil`. - Updates the `bin/parse` script to no longer raise it's own `ArgumentError` now that we raise a proper error. Fixes: ruby/prism#2207 https://github.com/ruby/prism/commit/b2f7494ff5 --- lib/prism/ffi.rb | 9 ++++- prism/extension.c | 53 +++++++++++++++++++------ prism/util/pm_string.c | 7 ---- test/prism/parse_test.rb | 85 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 21 deletions(-) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 12177450f4f429..617c469df65d70 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -160,8 +160,13 @@ def self.with(filepath, &block) pointer = FFI::MemoryPointer.new(SIZEOF) begin - raise unless LibRubyParser.pm_string_mapped_init(pointer, filepath) - yield new(pointer) + raise TypeError unless filepath.is_a?(String) + + if LibRubyParser.pm_string_mapped_init(pointer, filepath) + yield new(pointer) + else + raise SystemCallError.new(filepath, FFI.errno) + end ensure LibRubyParser.pm_string_free(pointer) pointer.free diff --git a/prism/extension.c b/prism/extension.c index 7cad38404e5374..c20ce5b5254b2a 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -1,5 +1,9 @@ #include "prism/extension.h" +#ifdef _WIN32 +#include +#endif + // NOTE: this file should contain only bindings. All non-trivial logic should be // in libprism so it can be shared its the various callers. @@ -212,20 +216,29 @@ string_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) /** * Read options for methods that look like (filepath, **options). */ -static bool +static void file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) { VALUE filepath; VALUE keywords; rb_scan_args(argc, argv, "1:", &filepath, &keywords); + Check_Type(filepath, T_STRING); + extract_options(options, filepath, keywords); - if (!pm_string_mapped_init(input, (const char *) pm_string_source(&options->filepath))) { + const char * string_source = (const char *) pm_string_source(&options->filepath); + + if (!pm_string_mapped_init(input, string_source)) { pm_options_free(options); - return false; - } - return true; +#ifdef _WIN32 + int e = rb_w32_map_errno(GetLastError()); +#else + int e = errno; +#endif + + rb_syserr_fail(e, string_source); + } } /******************************************************************************/ @@ -299,7 +312,8 @@ static VALUE dump_file(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE value = dump_input(&input, &options); pm_string_free(&input); @@ -609,7 +623,8 @@ static VALUE lex_file(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE value = parse_lex_input(&input, &options, false); pm_string_free(&input); @@ -710,7 +725,8 @@ static VALUE parse_file(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE value = parse_input(&input, &options); pm_string_free(&input); @@ -770,7 +786,8 @@ static VALUE parse_file_comments(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE value = parse_input_comments(&input, &options); pm_string_free(&input); @@ -824,7 +841,8 @@ static VALUE parse_lex_file(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE value = parse_lex_input(&input, &options, true); pm_string_free(&input); @@ -881,7 +899,8 @@ static VALUE parse_file_success_p(int argc, VALUE *argv, VALUE self) { pm_string_t input; pm_options_t options = { 0 }; - if (!file_options(argc, argv, &input, &options)) return Qnil; + + file_options(argc, argv, &input, &options); VALUE result = parse_input_success_p(&input, &options); pm_string_free(&input); @@ -959,7 +978,17 @@ profile_file(VALUE self, VALUE filepath) { pm_string_t input; const char *checked = check_string(filepath); - if (!pm_string_mapped_init(&input, checked)) return Qnil; + Check_Type(filepath, T_STRING); + + if (!pm_string_mapped_init(&input, checked)) { +#ifdef _WIN32 + int e = rb_w32_map_errno(GetLastError()); +#else + int e = errno; +#endif + + rb_syserr_fail(e, checked); + } pm_options_t options = { 0 }; pm_options_filepath_set(&options, checked); diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c index f4d3033a1b763e..e67fcee0ec1e34 100644 --- a/prism/util/pm_string.c +++ b/prism/util/pm_string.c @@ -65,7 +65,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { HANDLE file = CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) { - perror("CreateFile failed"); return false; } @@ -73,7 +72,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { DWORD file_size = GetFileSize(file, NULL); if (file_size == INVALID_FILE_SIZE) { CloseHandle(file); - perror("GetFileSize failed"); return false; } @@ -90,7 +88,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { HANDLE mapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); if (mapping == NULL) { CloseHandle(file); - perror("CreateFileMapping failed"); return false; } @@ -100,7 +97,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { CloseHandle(file); if (source == NULL) { - perror("MapViewOfFile failed"); return false; } @@ -110,7 +106,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { // Open the file for reading int fd = open(filepath, O_RDONLY); if (fd == -1) { - perror("open"); return false; } @@ -118,7 +113,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { struct stat sb; if (fstat(fd, &sb) == -1) { close(fd); - perror("fstat"); return false; } @@ -135,7 +129,6 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { source = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (source == MAP_FAILED) { - perror("Map failed"); return false; } diff --git a/test/prism/parse_test.rb b/test/prism/parse_test.rb index 26243150084dc0..3a2bc3716f328f 100644 --- a/test/prism/parse_test.rb +++ b/test/prism/parse_test.rb @@ -69,11 +69,96 @@ def test_parse_lex assert_equal 5, tokens.length end + def test_dump_file + assert_nothing_raised do + Prism.dump_file(__FILE__) + end + + error = assert_raise Errno::ENOENT do + Prism.dump_file("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.dump_file(nil) + end + end + + def test_lex_file + assert_nothing_raised do + Prism.lex_file(__FILE__) + end + + error = assert_raise Errno::ENOENT do + Prism.lex_file("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.lex_file(nil) + end + end + def test_parse_lex_file node, tokens = Prism.parse_lex_file(__FILE__).value assert_kind_of ProgramNode, node refute_empty tokens + + error = assert_raise Errno::ENOENT do + Prism.parse_lex_file("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.parse_lex_file(nil) + end + end + + def test_parse_file + node = Prism.parse_file(__FILE__).value + assert_kind_of ProgramNode, node + + error = assert_raise Errno::ENOENT do + Prism.parse_file("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.parse_file(nil) + end + end + + def test_parse_file_success + assert_predicate Prism.parse_file_comments(__FILE__), :any? + + error = assert_raise Errno::ENOENT do + Prism.parse_file_comments("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.parse_file_comments(nil) + end + end + + def test_parse_file_comments + assert_predicate Prism.parse_file_comments(__FILE__), :any? + + error = assert_raise Errno::ENOENT do + Prism.parse_file_comments("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + Prism.parse_file_comments(nil) + end end # To accurately compare against Ripper, we need to make sure that we're From 44f0dc622ae08e851127c0fec3cc545256b6c040 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 6 Feb 2024 14:56:49 -0500 Subject: [PATCH 008/142] YJIT: Allow popping before falling back Popping but not generating any code before returning `None` was allowed before fallbacks were introduced so this is restoring that support in the same way. The included test used to trip an assert due to popping too much. --- bootstraptest/test_yjit.rb | 11 +++++++++++ yjit/src/codegen.rs | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 786d2cc7dd985d..d5427588e012c0 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -4527,3 +4527,14 @@ def foo(a, b) = [a, b] arr = [1] foo(*arr, 2) } + +# pop before fallback +assert_normal_exit %q{ + class Foo + attr_reader :foo + + def try = foo(0, &nil) + end + + Foo.new.try +} diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index d7de29c5d3e71c..4eaeebd503a660 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7516,6 +7516,10 @@ fn gen_send_dynamic Opnd>( return None; } + // Rewind stack_size using ctx.with_stack_size to allow stack_size changes + // before you return None. + asm.ctx = asm.ctx.with_stack_size(jit.stack_size_for_pc); + // Save PC and SP to prepare for dynamic dispatch jit_prepare_non_leaf_call(jit, asm); From ae13f8532217cbe344a440b4df2329194e2dad5d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 14:32:57 -0500 Subject: [PATCH 009/142] Add test-all to prism --- .github/workflows/prism.yml | 30 ++++++++---------------- test/.excludes-prism/TestAssignment.rb | 1 + test/.excludes-prism/TestM17N.rb | 5 ++++ test/.excludes-prism/TestRequire.rb | 1 + test/.excludes-prism/TestRubyLiteral.rb | 1 + test/.excludes-prism/TestSetTraceFunc.rb | 3 +++ test/.excludes-prism/TestSyntax.rb | 1 + 7 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 test/.excludes-prism/TestAssignment.rb create mode 100644 test/.excludes-prism/TestM17N.rb create mode 100644 test/.excludes-prism/TestRequire.rb create mode 100644 test/.excludes-prism/TestRubyLiteral.rb create mode 100644 test/.excludes-prism/TestSetTraceFunc.rb create mode 100644 test/.excludes-prism/TestSyntax.rb diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index d8fbb8ee830eb0..151fd2c4966d3a 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -83,33 +83,23 @@ jobs: - run: $SETARCH make - - name: make btest + - name: make test run: | - make -s btest RUN_OPTS="$RUN_OPTS" + $SETARCH make -s test RUN_OPTS="$RUN_OPTS" timeout-minutes: 30 env: GNUMAKEFLAGS: '' RUBY_TESTOPTS: '-v --tty=no' RUN_OPTS: ${{ matrix.run_opts }} - continue-on-error: true - # - name: make test - # run: | - # $SETARCH make -s test RUN_OPTS="$RUN_OPTS" - # timeout-minutes: 30 - # env: - # GNUMAKEFLAGS: '' - # RUBY_TESTOPTS: '-v --tty=no' - # RUN_OPTS: ${{ matrix.run_opts }} - - # - name: make test-all - # run: | - # $SETARCH make -s test-all RUN_OPTS="$RUN_OPTS" - # timeout-minutes: 40 - # env: - # GNUMAKEFLAGS: '' - # RUBY_TESTOPTS: '-q --tty=no' - # RUN_OPTS: ${{ matrix.run_opts }} + - name: make test-all + run: | + $SETARCH make -s test-all RUN_OPTS="$RUN_OPTS" + timeout-minutes: 40 + env: + GNUMAKEFLAGS: '' + RUBY_TESTOPTS: '-q --tty=no --excludes-dir="../src/test/.excludes-prism" --exclude="test_ast.rb" --exclude="test_regexp.rb" --exclude="error_highlight/test_error_highlight.rb"' + RUN_OPTS: ${{ matrix.run_opts }} # - name: make test-spec # run: | diff --git a/test/.excludes-prism/TestAssignment.rb b/test/.excludes-prism/TestAssignment.rb new file mode 100644 index 00000000000000..23a1b4c5e77e5f --- /dev/null +++ b/test/.excludes-prism/TestAssignment.rb @@ -0,0 +1 @@ +exclude(:test_massign_order, "https://github.com/ruby/prism/issues/2370") diff --git a/test/.excludes-prism/TestM17N.rb b/test/.excludes-prism/TestM17N.rb new file mode 100644 index 00000000000000..c49125bd1f6891 --- /dev/null +++ b/test/.excludes-prism/TestM17N.rb @@ -0,0 +1,5 @@ +exclude(:test_regexp_embed, "https://github.com/ruby/prism/issues/1997") +exclude(:test_dynamic_eucjp_regexp, "https://github.com/ruby/prism/issues/1997") +exclude(:test_regexp_ascii, "https://github.com/ruby/prism/issues/1997") +exclude(:test_dynamic_utf8_regexp, "https://github.com/ruby/prism/issues/1997") +exclude(:test_dynamic_sjis_regexp, "https://github.com/ruby/prism/issues/1997") diff --git a/test/.excludes-prism/TestRequire.rb b/test/.excludes-prism/TestRequire.rb new file mode 100644 index 00000000000000..78997a42b75bab --- /dev/null +++ b/test/.excludes-prism/TestRequire.rb @@ -0,0 +1 @@ +exclude(:test_require_nonascii_path_shift_jis, "unknown") diff --git a/test/.excludes-prism/TestRubyLiteral.rb b/test/.excludes-prism/TestRubyLiteral.rb new file mode 100644 index 00000000000000..0ea7c9362cb3e8 --- /dev/null +++ b/test/.excludes-prism/TestRubyLiteral.rb @@ -0,0 +1 @@ +exclude(:test_string, "https://github.com/ruby/prism/issues/2331") diff --git a/test/.excludes-prism/TestSetTraceFunc.rb b/test/.excludes-prism/TestSetTraceFunc.rb new file mode 100644 index 00000000000000..3999fd3884d803 --- /dev/null +++ b/test/.excludes-prism/TestSetTraceFunc.rb @@ -0,0 +1,3 @@ +exclude(:test_tracepoint_nested_enabled_with_target, "unknown") +exclude(:test_allow_reentry, "unknown") +exclude(:test_tp_rescue, "unknown") diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb new file mode 100644 index 00000000000000..bfc9ff9d9e7a98 --- /dev/null +++ b/test/.excludes-prism/TestSyntax.rb @@ -0,0 +1 @@ +exclude(:test_it, "https://github.com/ruby/prism/issues/2323") From e34505c631349f8f8b548953d392191b96e9d29c Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Tue, 6 Feb 2024 10:54:21 +0000 Subject: [PATCH 010/142] [ruby/prism] More visitors and tests for RipperCompat Part of issue #2354 https://github.com/ruby/prism/commit/cb28edae34 --- lib/prism/ripper_compat.rb | 69 ++++++++++++++++++++++++++++---- test/prism/ripper_compat_test.rb | 26 ++++++++++++ 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 5bd3088a6aeede..739d587541ba74 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -108,17 +108,51 @@ def parse # Visit a CallNode node. def visit_call_node(node) - if !node.message.match?(/^[[:alpha:]_]/) && node.opening_loc.nil? && node.arguments&.arguments&.length == 1 + bounds(node.location) + + if node.message.match?(/^[[:alpha:]_]/) + val = on_ident(node.message) + + return on_vcall(val) + end + + if node.opening_loc.nil? left = visit(node.receiver) - right = visit(node.arguments.arguments.first) + if node.arguments&.arguments&.length == 1 + right = visit(node.arguments.arguments.first) + + on_binary(left, node.name, right) + elsif !node.arguments || node.arguments.empty? + on_unary(node.name, left) + else + raise NotImplementedError, "More than two arguments for operator" + end + else + # This is a method call, not an operator + raise NotImplementedError, "Non-nil opening_loc" + end + end - bounds(node.location) - on_binary(left, node.name, right) + # Visit a RangeNode + def visit_range_node(node) + bounds(node.location) + left = visit(node.left) + right = visit(node.right) + + if node.operator_loc.slice == "..." + on_dot3(left, right) else - raise NotImplementedError + on_dot2(left, right) end end + # Visit a ParenthesesNode + def visit_parentheses_node(node) + bounds(node.location) + val = visit(node.body) + on_paren(val) + end + # Visit a FloatNode node. def visit_float_node(node) bounds(node.location) @@ -133,8 +167,22 @@ def visit_imaginary_node(node) # Visit an IntegerNode node. def visit_integer_node(node) - bounds(node.location) - on_int(node.slice) + text = node.slice + loc = node.location + if text[0] == "-" + # TODO: This is ugly. We test that a newline between - and 7 doesn't give an error, but this could still be bad somehow. + bounds_line_col(loc.start_line, loc.start_column + 1) + on_int_val = on_int(text[1..-1]) + bounds(node.location) + if RUBY_ENGINE == "jruby" + on_unary(:-, on_int_val) + else + on_unary(:-@, on_int_val) + end + else + bounds(node.location) + on_int(text) + end end # Visit a RationalNode node. @@ -184,6 +232,13 @@ def bounds(location) @column = location.start_column end + # If we need to do something unusual, we can directly update the line number + # and column to reflect the current node. + def bounds_line_col(lineno, column) + @lineno = lineno + @column = column + end + # Lazily initialize the parse result. def result @result ||= Prism.parse(source) diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index e30f3824e2d0eb..0bdef530388a1f 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -10,6 +10,32 @@ def test_binary assert_equivalent("6 / 7; 8 % 9") end + def test_unary + assert_equivalent("-7") + end + + def test_unary_parens + assert_equivalent("-(7)") + assert_equivalent("(-7)") + assert_equivalent("(-\n7)") + end + + def test_binary_parens + assert_equivalent("(3 + 7) * 4") + end + + def test_ident + assert_equivalent("foo") + end + + def test_range + assert_equivalent("(...2)") + assert_equivalent("(..2)") + assert_equivalent("(1...2)") + assert_equivalent("(1..2)") + assert_equivalent("(foo..-7)") + end + private def assert_equivalent(source) From 64b6a018a38f200c957fdbbe7d0cbe0e64781c9f Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Sat, 3 Feb 2024 22:35:44 +0900 Subject: [PATCH 011/142] Fix test session reuse but expire (#9824) * OpenSSL 3.2.1 30 Jan 2024 is also broken Import 45064610725ddd81a5ea3775da35aa46985bc789 from ruby_3_3 branch tentatively. --- test/net/http/test_https.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index ccfa48b2a44ea8..6b3171d265997e 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -167,7 +167,7 @@ def test_session_reuse def test_session_reuse_but_expire # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h') - omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 3.2.0') + omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 3.2.') http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true From 2dba441397d338617e1b605104d8b42b5311a482 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 6 Feb 2024 21:31:12 -0500 Subject: [PATCH 012/142] [ruby/prism] Even more ripper compat https://github.com/ruby/prism/commit/47a602dc1c --- lib/prism/ripper_compat.rb | 127 ++++++++++++++++++------------- test/prism/ripper_compat_test.rb | 18 ++++- 2 files changed, 92 insertions(+), 53 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 739d587541ba74..912114cfef5373 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -97,6 +97,8 @@ def parse result.errors.each do |error| on_parse_error(error.message) end + + nil else result.value.accept(self) end @@ -106,14 +108,22 @@ def parse # Visitor methods ############################################################################ - # Visit a CallNode node. - def visit_call_node(node) + # Visit an ArrayNode node. + def visit_array_node(node) + elements = visit_elements(node.elements) unless node.elements.empty? bounds(node.location) + on_array(elements) + end - if node.message.match?(/^[[:alpha:]_]/) - val = on_ident(node.message) + # Visit a CallNode node. + def visit_call_node(node) + if node.variable_call? + if node.message.match?(/^[[:alpha:]_]/) + bounds(node.message_loc) + return on_vcall(on_ident(node.message)) + end - return on_vcall(val) + raise NotImplementedError, "Non-alpha variable call" end if node.opening_loc.nil? @@ -128,67 +138,61 @@ def visit_call_node(node) raise NotImplementedError, "More than two arguments for operator" end else - # This is a method call, not an operator raise NotImplementedError, "Non-nil opening_loc" end end - # Visit a RangeNode - def visit_range_node(node) - bounds(node.location) - left = visit(node.left) - right = visit(node.right) - - if node.operator_loc.slice == "..." - on_dot3(left, right) - else - on_dot2(left, right) - end - end - - # Visit a ParenthesesNode - def visit_parentheses_node(node) - bounds(node.location) - val = visit(node.body) - on_paren(val) - end - # Visit a FloatNode node. def visit_float_node(node) - bounds(node.location) - on_float(node.slice) + visit_number(node) { |text| on_float(text) } end # Visit a ImaginaryNode node. def visit_imaginary_node(node) - bounds(node.location) - on_imaginary(node.slice) + visit_number(node) { |text| on_imaginary(text) } end # Visit an IntegerNode node. def visit_integer_node(node) - text = node.slice - loc = node.location - if text[0] == "-" - # TODO: This is ugly. We test that a newline between - and 7 doesn't give an error, but this could still be bad somehow. - bounds_line_col(loc.start_line, loc.start_column + 1) - on_int_val = on_int(text[1..-1]) - bounds(node.location) - if RUBY_ENGINE == "jruby" - on_unary(:-, on_int_val) + visit_number(node) { |text| on_int(text) } + end + + # Visit a ParenthesesNode node. + def visit_parentheses_node(node) + body = + if node.body.nil? + on_stmts_add(on_stmts_new, on_void_stmt) else - on_unary(:-@, on_int_val) + visit(node.body) end + + bounds(node.location) + on_paren(body) + end + + # Visit a ProgramNode node. + def visit_program_node(node) + statements = visit(node.statements) + bounds(node.location) + on_program(statements) + end + + # Visit a RangeNode node. + def visit_range_node(node) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + if node.exclude_end? + on_dot3(left, right) else - bounds(node.location) - on_int(text) + on_dot2(left, right) end end # Visit a RationalNode node. def visit_rational_node(node) - bounds(node.location) - on_rational(node.slice) + visit_number(node) { |text| on_rational(text) } end # Visit a StatementsNode node. @@ -199,13 +203,6 @@ def visit_statements_node(node) end end - # Visit a ProgramNode node. - def visit_program_node(node) - statements = visit(node.statements) - bounds(node.location) - on_program(statements) - end - ############################################################################ # Entrypoints for subclasses ############################################################################ @@ -222,6 +219,32 @@ def self.sexp(source) private + # Visit a list of elements, like the elements of an array or arguments. + def visit_elements(elements) + bounds(elements.first.location) + elements.inject(on_args_new) do |args, element| + on_args_add(args, visit(element)) + end + end + + # Visit a node that represents a number. We need to explicitly handle the + # unary - operator. + def visit_number(node) + slice = node.slice + location = node.location + + if slice[0] == "-" + bounds_values(location.start_line, location.start_column + 1) + value = yield slice[1..-1] + + bounds(node.location) + on_unary(RUBY_ENGINE == "jruby" ? :- : :-@, value) + else + bounds(location) + yield slice + end + end + # This method is responsible for updating lineno and column information # to reflect the current node. # @@ -234,7 +257,7 @@ def bounds(location) # If we need to do something unusual, we can directly update the line number # and column to reflect the current node. - def bounds_line_col(lineno, column) + def bounds_values(lineno, column) @lineno = lineno @column = column end diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index 0bdef530388a1f..8ca8545add34d1 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -36,10 +36,26 @@ def test_range assert_equivalent("(foo..-7)") end + def test_parentheses + assert_equivalent("()") + assert_equivalent("(1)") + assert_equivalent("(1; 2)") + end + + def test_numbers + assert_equivalent("[1, -1, +1, 1.0, -1.0, +1.0]") + assert_equivalent("[1r, -1r, +1r, 1.5r, -1.5r, +1.5r]") + assert_equivalent("[1i, -1i, +1i, 1.5i, -1.5i, +1.5i]") + assert_equivalent("[1ri, -1ri, +1ri, 1.5ri, -1.5ri, +1.5ri]") + end + private def assert_equivalent(source) - assert_equal Ripper.sexp_raw(source), RipperCompat.sexp_raw(source) + expected = Ripper.sexp_raw(source) + + refute_nil expected + assert_equal expected, RipperCompat.sexp_raw(source) end end end From 5ddf4f5c95ae24eda6a89afb885410991f010abd Mon Sep 17 00:00:00 2001 From: Kim Emmanuel Date: Sat, 27 Jan 2024 02:58:45 -0300 Subject: [PATCH 013/142] [rubygems/rubygems] fix Gem::Dependency#to_spec returning nil when prerelease is the only available version https://github.com/rubygems/rubygems/commit/a7dcc7214b --- lib/rubygems/dependency.rb | 2 +- test/rubygems/test_gem_dependency.rb | 10 ++++++++++ test/rubygems/test_gem_specification.rb | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 00eff2dfe7cfaf..2f0dcba0eab6df 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -330,7 +330,7 @@ def to_spec unless prerelease? # Move prereleases to the end of the list for >= 0 requirements pre, matches = matches.partition {|spec| spec.version.prerelease? } - matches += pre if requirement == Gem::Requirement.default + matches += pre if requirement == Gem::Requirement.default || matches.empty? end matches.first diff --git a/test/rubygems/test_gem_dependency.rb b/test/rubygems/test_gem_dependency.rb index 6ac03fc0e2ac71..2a989a55513b99 100644 --- a/test/rubygems/test_gem_dependency.rb +++ b/test/rubygems/test_gem_dependency.rb @@ -394,6 +394,16 @@ def test_to_specs_indicates_total_gem_set_size assert_match "Could not find 'b' (= 2.0) among 1 total gem(s)", e.message end + def test_to_spec_with_only_prereleases + a_2_a_1 = util_spec "a", "2.a1" + a_2_a_2 = util_spec "a", "2.a2" + install_specs a_2_a_1, a_2_a_2 + + a_dep = dep "a", ">= 1" + + assert_equal a_2_a_2, a_dep.to_spec + end + def test_identity assert_equal dep("a", "= 1").identity, :released assert_equal dep("a", "= 1.a").identity, :complete diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 3c37f08efad64c..977e8b29658e06 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -3801,6 +3801,13 @@ def test_find_by_name_with_only_prereleases assert Gem::Specification.find_by_name "q" end + def test_find_by_name_with_only_prereleases_with_requirements + q = util_spec "q", "2.a" + install_specs q + + assert Gem::Specification.find_by_name "q", ">= 1" + end + def test_find_by_name_prerelease b = util_spec "b", "2.a" From aaef443a59a8d7b3631fa48a8d0a3a03744e0f96 Mon Sep 17 00:00:00 2001 From: Kim Emmanuel Date: Mon, 29 Jan 2024 15:46:23 -0300 Subject: [PATCH 014/142] [rubygems/rubygems] release requirement may load prerelease when sole option https://github.com/rubygems/rubygems/commit/7990771939 --- test/rubygems/test_kernel.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/rubygems/test_kernel.rb b/test/rubygems/test_kernel.rb index 4c3d2d20c65cc9..592cb655b38ff9 100644 --- a/test/rubygems/test_kernel.rb +++ b/test/rubygems/test_kernel.rb @@ -52,10 +52,20 @@ def test_gem_overlapping assert_equal 1, $:.count {|p| p.include?("a-1/lib") } end - def test_gem_prerelease + def test_gem_prerelease_is_the_only_available quick_gem "d", "1.1.a" - refute gem("d", ">= 1"), "release requirement must not load prerelease" - assert gem("d", ">= 1.a"), "prerelease requirement may load prerelease" + + assert gem("d", ">= 1"), "release requirement may load prerelease when sole option" + assert $:.one? {|p| p.include?("1.1.a/lib") } + end + + def test_release_favored_over_prerelease + quick_gem "d", "1.1.a" + quick_gem "d", "1.2" + gem("d", ">= 1") + + refute $:.any? {|p| p.include?("1.1.a/lib") } + assert $:.one? {|p| p.include?("1.2/lib") } end def test_gem_env_req From 0edf5a714bfe92431a6b53d2931a504d8e584b7e Mon Sep 17 00:00:00 2001 From: Kim Emmanuel Date: Wed, 31 Jan 2024 18:29:06 -0300 Subject: [PATCH 015/142] [rubygems/rubygems] #to_spec must fallback for prereleases always https://github.com/rubygems/rubygems/commit/6302798a32 --- lib/rubygems/dependency.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 2f0dcba0eab6df..d1bf074441e5e9 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -328,9 +328,9 @@ def to_spec return active if active unless prerelease? - # Move prereleases to the end of the list for >= 0 requirements + # Consider prereleases only as a fallback pre, matches = matches.partition {|spec| spec.version.prerelease? } - matches += pre if requirement == Gem::Requirement.default || matches.empty? + matches = pre if matches.empty? end matches.first From 8bd83bb133bb2ef5616e49b4a207e8ec59a83472 Mon Sep 17 00:00:00 2001 From: Kim Emmanuel Date: Wed, 31 Jan 2024 18:53:17 -0300 Subject: [PATCH 016/142] [rubygems/rubygems] fix flaky tests https://github.com/rubygems/rubygems/commit/0e87ae032d --- test/rubygems/test_kernel.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/rubygems/test_kernel.rb b/test/rubygems/test_kernel.rb index 592cb655b38ff9..d862b26fe98d2a 100644 --- a/test/rubygems/test_kernel.rb +++ b/test/rubygems/test_kernel.rb @@ -56,7 +56,7 @@ def test_gem_prerelease_is_the_only_available quick_gem "d", "1.1.a" assert gem("d", ">= 1"), "release requirement may load prerelease when sole option" - assert $:.one? {|p| p.include?("1.1.a/lib") } + assert $:.one? {|p| p.include?("/d-1.1.a/lib") } end def test_release_favored_over_prerelease @@ -64,8 +64,8 @@ def test_release_favored_over_prerelease quick_gem "d", "1.2" gem("d", ">= 1") - refute $:.any? {|p| p.include?("1.1.a/lib") } - assert $:.one? {|p| p.include?("1.2/lib") } + refute $:.any? {|p| p.include?("/d-1.1.a/lib") } + assert $:.one? {|p| p.include?("/d-1.2/lib") } end def test_gem_env_req From 75aaeb35b82da26359b9418d2963384d0c55839c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 7 Feb 2024 11:00:47 +0900 Subject: [PATCH 017/142] [Bug #20239] Fix overflow at down-casting --- regenc.c | 2 +- regexec.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/regenc.c b/regenc.c index fc131d2533cb6a..eb523e1ae530a8 100644 --- a/regenc.c +++ b/regenc.c @@ -57,7 +57,7 @@ onigenc_mbclen(const OnigUChar* p,const OnigUChar* e, OnigEncoding enc) int ret = ONIGENC_PRECISE_MBC_ENC_LEN(enc, p, e); if (ONIGENC_MBCLEN_CHARFOUND_P(ret)) { ret = ONIGENC_MBCLEN_CHARFOUND_LEN(ret); - if (ret > (int)(e - p)) ret = (int)(e - p); // just for case + if (p + ret > e) ret = (int)(e - p); // just for case return ret; } else if (ONIGENC_MBCLEN_NEEDMORE_P(ret)) { diff --git a/regexec.c b/regexec.c index 81d0ea6a6b0109..9e6f503ed03f7f 100644 --- a/regexec.c +++ b/regexec.c @@ -3521,7 +3521,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, n = pend - pstart; DATA_ENSURE(n); sprev = s; - STRING_CMP_IC(case_fold_flag, pstart, &s, (int)n, end); + STRING_CMP_IC(case_fold_flag, pstart, &s, n, end); while (sprev + (len = enclen(encode, sprev, end)) < s) sprev += len; From d95d3484a90a985b971ef4c55762847d92b6c81a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 16:05:14 +0900 Subject: [PATCH 018/142] omit tests related legacy provider It failed with recent update of FreeBSD https://rubyci.s3.amazonaws.com/freebsd13/ruby-master/log/20240207T023002Z.fail.html.gz --- test/openssl/test_provider.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb index d0e66785870eb0..80cf96771c0e97 100644 --- a/test/openssl/test_provider.rb +++ b/test/openssl/test_provider.rb @@ -11,7 +11,9 @@ def test_openssl_provider_name_inspect end; end - def test_openssl_provider_names + def test_openssl_provider_name + omit if /freebsd/ =~ RUBY_PLATFORM + with_openssl <<-'end;' legacy_provider = OpenSSL::Provider.load("legacy") assert_equal(2, OpenSSL::Provider.provider_names.size) @@ -33,6 +35,8 @@ def test_unloaded_openssl_provider end def test_openssl_legacy_provider + omit if /freebsd/ =~ RUBY_PLATFORM + with_openssl(<<-'end;') OpenSSL::Provider.load("legacy") algo = "RC4" From 8407044388e5e7cb533d98f8b4a442db6966f5ea Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 16:30:50 +0900 Subject: [PATCH 019/142] Update latest version of CodeQL --- .github/workflows/codeql-analysis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 47104672fe100b..a98d414462c8d9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -67,15 +67,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 + uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 + uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 + uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: category: '/language:${{ matrix.language }}' upload: False @@ -86,7 +86,7 @@ jobs: continue-on-error: true - name: filter-sarif - uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0 + uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1 with: patterns: | +**/*.rb @@ -109,7 +109,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 + uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true From 66d6695f7fe4b7070ae3066d58b060225f52a54c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 16:40:08 +0900 Subject: [PATCH 020/142] Try to run with large runner --- .github/workflows/codeql-analysis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a98d414462c8d9..93f7ad083e07e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,7 @@ permissions: # added using https://github.com/step-security/secure-workflows jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: macos-arm-oss permissions: actions: read # for github/codeql-action/init to get workflow details contents: read # for actions/checkout to fetch code @@ -59,13 +59,10 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install libraries - uses: ./.github/actions/setup/ubuntu + uses: ./.github/actions/setup/macos - uses: ./.github/actions/setup/directories - - name: Remove an obsolete rubygems vendored file - run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - - name: Initialize CodeQL uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: From 42c36269403baac67b0d5dc1d6d6e31168cf6a1f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 16:57:54 +0900 Subject: [PATCH 021/142] The default ram size is 13GB at macos runner --- .github/workflows/codeql-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 93f7ad083e07e3..a7303e474aec81 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -77,7 +77,6 @@ jobs: category: '/language:${{ matrix.language }}' upload: False output: sarif-results - ram: 8192 # CodeQL randomly hits `OutOfMemoryError "Java heap space"`. # GitHub recommends running a larger runner to fix it, but we don't pay for it. continue-on-error: true From 565ef06e917c6f326f8d528dc8fa6c6de88a8753 Mon Sep 17 00:00:00 2001 From: KJ Tsanaktsidis Date: Wed, 7 Feb 2024 18:35:14 +1100 Subject: [PATCH 022/142] Ignore _odr_asan symbols in leaked-globals ASAN includes these to detect violations of the ODR rule. [Bug #20221] --- tool/leaked-globals | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/leaked-globals b/tool/leaked-globals index b80ae3907376ed..4aa2aff996256e 100755 --- a/tool/leaked-globals +++ b/tool/leaked-globals @@ -88,6 +88,7 @@ Pipe.new(NM + ARGV).each do |line| next unless n.sub!(/^#{SYMBOL_PREFIX}/o, "") next if n.include?(".") next if !so and n.start_with?("___asan_") + next if !so and n.start_with?("__odr_asan_") case n when /\A(?:Init_|InitVM_|pm_|[Oo]nig|dln_|coroutine_)/ next From 5d5d27a60d7649e3ccc18e741a1581ef22effa0f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 17:28:09 +0900 Subject: [PATCH 023/142] readline-ext is extracted bundled gems at Ruby 3.3 --- .github/actions/setup/macos/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup/macos/action.yml b/.github/actions/setup/macos/action.yml index 3649a648760324..be74e30c6d6996 100644 --- a/.github/actions/setup/macos/action.yml +++ b/.github/actions/setup/macos/action.yml @@ -13,12 +13,12 @@ runs: - name: brew shell: bash run: | - brew install --quiet gmp libffi openssl@1.1 zlib autoconf automake libtool readline + brew install --quiet gmp libffi openssl@1.1 zlib autoconf automake libtool - name: Set ENV shell: bash run: | - for lib in openssl@1.1 readline; do + for lib in openssl@1.1; do CONFIGURE_ARGS="${CONFIGURE_ARGS:+$CONFIGURE_ARGS }--with-${lib%@*}-dir=$(brew --prefix $lib)" done echo CONFIGURE_ARGS="${CONFIGURE_ARGS}" >> $GITHUB_ENV From 78898c53c76d7b5b741334af74275da6ea77e214 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 17:28:44 +0900 Subject: [PATCH 024/142] Refer gmp to macOS build --- .github/actions/setup/macos/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/macos/action.yml b/.github/actions/setup/macos/action.yml index be74e30c6d6996..896930de84d161 100644 --- a/.github/actions/setup/macos/action.yml +++ b/.github/actions/setup/macos/action.yml @@ -18,7 +18,7 @@ runs: - name: Set ENV shell: bash run: | - for lib in openssl@1.1; do + for lib in openssl@1.1 gmp; do CONFIGURE_ARGS="${CONFIGURE_ARGS:+$CONFIGURE_ARGS }--with-${lib%@*}-dir=$(brew --prefix $lib)" done echo CONFIGURE_ARGS="${CONFIGURE_ARGS}" >> $GITHUB_ENV From e965c5a174872d40afb1a90d5070decd2caedeb5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 7 Feb 2024 18:13:00 +0900 Subject: [PATCH 025/142] Keep cpp build with ubuntu-latest --- .github/workflows/codeql-analysis.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a7303e474aec81..11f0a0f025d599 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,7 @@ permissions: # added using https://github.com/step-security/secure-workflows jobs: analyze: name: Analyze - runs-on: macos-arm-oss + runs-on: ${{ matrix.os }} permissions: actions: read # for github/codeql-action/init to get workflow details contents: read # for actions/checkout to fetch code @@ -52,17 +52,32 @@ jobs: strategy: fail-fast: false matrix: - language: ['cpp', 'ruby'] + include: + - language: cpp + os: ubuntu-latest + ram: 8192 + - language: ruby + os: macos-arm-oss + ram: 13312 steps: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install libraries + if: ${{ matrix.os == 'macos-arm-oss' }} uses: ./.github/actions/setup/macos + - name: Install libraries + if : ${{ matrix.os == 'ubuntu-latest' }} + uses: ./.github/actions/setup/ubuntu + - uses: ./.github/actions/setup/directories + - name: Remove an obsolete rubygems vendored file + if: ${{ matrix.os == 'ubuntu-latest' }} + run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb + - name: Initialize CodeQL uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 with: @@ -77,6 +92,7 @@ jobs: category: '/language:${{ matrix.language }}' upload: False output: sarif-results + ram: ${{ matrix.ram }} # CodeQL randomly hits `OutOfMemoryError "Java heap space"`. # GitHub recommends running a larger runner to fix it, but we don't pay for it. continue-on-error: true From 9ebaf7a8ef231632c98a944f1214b902764ae3ab Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 7 Feb 2024 18:18:57 +0900 Subject: [PATCH 026/142] Run CodeQL using macos-arm-oss only on ruby/ruby --- .github/workflows/codeql-analysis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 11f0a0f025d599..be4d2e25942cfd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,8 @@ jobs: security-events: write # for github/codeql-action/autobuild to send a status report # CodeQL fails to run pull requests from dependabot due to missing write access to upload results. if: >- - ${{!(false + ${{github.repository == 'ruby/ruby' && + !(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') || contains(github.event.pull_request.labels.*.name, 'Documentation') From 164c18af7b4a36dd7f6eea499e297c842ccb1585 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 7 Feb 2024 09:39:57 -0500 Subject: [PATCH 027/142] [ruby/prism] Correct handle recover parameters on tokenize for parser translation https://github.com/ruby/prism/commit/63979de21d --- lib/prism/translation/parser.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 60b62186ba2270..6723216d0057c2 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -68,17 +68,23 @@ def parse_with_comments(source_buffer) # Parses a source buffer and returns the AST, the source code comments, # and the tokens emitted by the lexer. - def tokenize(source_buffer, _recover = false) + def tokenize(source_buffer, recover = false) @source_buffer = source_buffer source = source_buffer.source offset_cache = build_offset_cache(source) - result = unwrap(Prism.parse_lex(source, filepath: source_buffer.name), offset_cache) + result = + begin + unwrap(Prism.parse_lex(source, filepath: source_buffer.name), offset_cache) + rescue ::Parser::SyntaxError + raise if !recover + end program, tokens = result.value + ast = build_ast(program, offset_cache) if result.success? [ - build_ast(program, offset_cache), + ast, build_comments(result.comments, offset_cache), build_tokens(tokens, offset_cache) ] From 5f4245e74b84e1435d51f45a43717a5d33aef4cb Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 7 Feb 2024 14:59:06 +0000 Subject: [PATCH 028/142] [ruby/irb] Polish tracer integration and tests (https://github.com/ruby/irb/pull/864) * Remove useless ivar * Simplify tracer test setup * Treat tracer like a normal development dependency * Only require ext/tracer when value is truthy * Make tracer integration skip IRB traces https://github.com/ruby/irb/commit/a97a4129a7 --- lib/irb/context.rb | 6 ++-- lib/irb/ext/tracer.rb | 7 +++++ lib/irb/init.rb | 3 -- test/irb/test_tracer.rb | 70 +++++++++++++++++++---------------------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 5b8791c3ba7e05..e30125f46bb859 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -61,7 +61,7 @@ def initialize(irb, workspace = nil, input_method = nil) @io = nil self.inspect_mode = IRB.conf[:INSPECT_MODE] - self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER] + self.use_tracer = IRB.conf[:USE_TRACER] self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER] self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY] @@ -162,8 +162,8 @@ def initialize(irb, workspace = nil, input_method = nil) private_constant :KEYWORD_ALIASES def use_tracer=(val) - require_relative "ext/tracer" - @use_tracer = val + require_relative "ext/tracer" if val + IRB.conf[:USE_TRACER] = val end private def build_completor diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index 53b2e62245a6b5..d498b5320b91ce 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -13,6 +13,13 @@ end module IRB + class CallTracer < ::CallTracer + IRB_DIR = File.expand_path('../..', __dir__) + + def skip?(tp) + super || tp.path.match?(IRB_DIR) || tp.path.match?('') + end + end class WorkSpace alias __evaluate__ evaluate # Evaluate the context of this workspace and use the Tracer library to diff --git a/lib/irb/init.rb b/lib/irb/init.rb index a434ab23e26b65..8acfdea8e197fc 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -62,9 +62,6 @@ def IRB.setup(ap_path, argv: ::ARGV) # @CONF default setting def IRB.init_config(ap_path) - # class instance variables - @TRACER_INITIALIZED = false - # default configurations unless ap_path and @CONF[:AP_NAME] ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb index f8da066b6351c0..199c9586b141ab 100644 --- a/test/irb/test_tracer.rb +++ b/test/irb/test_tracer.rb @@ -10,7 +10,9 @@ class ContextWithTracerIntegrationTest < IntegrationTestCase def setup super - @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') + omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby" + + @envs.merge!("NO_COLOR" => "true") end def example_ruby_file @@ -29,54 +31,30 @@ def bar(obj) RUBY end - def test_use_tracer_is_disabled_by_default + def test_use_tracer_enabled_when_gem_is_unavailable write_rc <<~RUBY - IRB.conf[:USE_TRACER] = false + # Simulate the absence of the tracer gem + ::Kernel.send(:alias_method, :irb_original_require, :require) + + ::Kernel.define_method(:require) do |name| + raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer") + ::Kernel.send(:irb_original_require, name) + end + + IRB.conf[:USE_TRACER] = true RUBY write_ruby example_ruby_file output = run_ruby_file do type "bar(Foo)" - type "exit!" + type "exit" end - assert_nil IRB.conf[:USER_TRACER] - assert_not_include(output, "#depth:") - assert_not_include(output, "Foo.foo") - end - - def test_use_tracer_enabled_when_gem_is_unavailable - begin - gem 'tracer' - omit "Skipping because 'tracer' gem is available." - rescue Gem::LoadError - write_rc <<~RUBY - IRB.conf[:USE_TRACER] = true - RUBY - - write_ruby example_ruby_file - - output = run_ruby_file do - type "bar(Foo)" - type "exit!" - end - - assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") - end + assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") end def test_use_tracer_enabled_when_gem_is_available - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0') - omit "Ruby version before 3.1.0 does not support Tracer integration. Skipping this test." - end - - begin - gem 'tracer' - rescue Gem::LoadError - omit "Skipping because 'tracer' gem is not available. Enable with WITH_TRACER=true." - end - write_rc <<~RUBY IRB.conf[:USE_TRACER] = true RUBY @@ -85,13 +63,29 @@ def test_use_tracer_enabled_when_gem_is_available output = run_ruby_file do type "bar(Foo)" - type "exit!" + type "exit" end assert_include(output, "Object#bar at") assert_include(output, "Foo.foo at") assert_include(output, "Foo.foo #=> 100") assert_include(output, "Object#bar #=> 100") + + # Test that the tracer output does not include IRB's own files + assert_not_include(output, "irb/workspace.rb") + end + + def test_use_tracer_is_disabled_by_default + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit" + end + + assert_not_include(output, "#depth:") + assert_not_include(output, "Foo.foo") end + end end From aed052ce9d9eeeccecb12c444aa7327d1e078a51 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 7 Feb 2024 10:00:35 -0500 Subject: [PATCH 029/142] [PRISM] Revert incorrect frozen string literal handling --- iseq.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/iseq.c b/iseq.c index 440270bd2957bc..e2e9b4d6e7e937 100644 --- a/iseq.c +++ b/iseq.c @@ -1002,15 +1002,8 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa .end_pos = { .lineno = (int) end.line, .column = (int) end.column } }; - rb_compile_option_t *current_option = (rb_compile_option_t *) option; - if (node->parser->frozen_string_literal) { - rb_compile_option_t new_option = *option; - new_option.frozen_string_literal = true; - current_option = &new_option; - } - prepare_iseq_build(iseq, name, path, realpath, first_lineno, &code_location, -1, - parent, isolated_depth, type, Qnil, current_option); + parent, isolated_depth, type, Qnil, option); pm_iseq_compile_node(iseq, node); finish_iseq_build(iseq); From aad3c36bdfe68c429cf612542a7eb3c94c17c483 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 5 Feb 2024 15:41:46 -0500 Subject: [PATCH 030/142] [ruby/prism] Support for Ruby 2.7 https://github.com/ruby/prism/commit/1a15b70a8e --- lib/prism/lex_compat.rb | 17 +++- lib/prism/parse_result.rb | 3 +- lib/prism/prism.gemspec | 2 +- lib/prism/translation/parser/compiler.rb | 22 +++-- prism/templates/lib/prism/node.rb.erb | 3 +- test/prism/errors_test.rb | 116 ++++++++++++----------- test/prism/test_helper.rb | 13 ++- 7 files changed, 106 insertions(+), 70 deletions(-) diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 2c23933714a537..0cc63cd94481b2 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -860,7 +860,7 @@ def result previous = [] results = [] - Ripper.lex(source, raise_errors: true).each do |token| + lex(source).each do |token| case token[1] when :on_sp # skip @@ -886,6 +886,21 @@ def result results end + + private + + if Ripper.method(:lex).parameters.assoc(:keyrest) + def lex(source) + Ripper.lex(source, raise_errors: true) + end + else + def lex(source) + ripper = Ripper::Lexer.new(source) + ripper.lex.tap do |result| + raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + end + end + end end private_constant :LexRipper diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index b67fe0617de2dd..a27f30d43b5684 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -91,7 +91,8 @@ def compute_offsets(code) class Location # A Source object that is used to determine more information from the given # offset and length. - protected attr_reader :source + attr_reader :source + protected :source # The byte offset from the beginning of the source where this location # starts. diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 8754f4c969e259..060ab662a47a6b 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/prism" spec.license = "MIT" - spec.required_ruby_version = ">= 3.0.0" + spec.required_ruby_version = ">= 2.7.0" spec.require_paths = ["lib"] spec.files = [ diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 45a51911545a35..264d85c2614186 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -1062,12 +1062,22 @@ def visit_local_variable_target_node(node) # foo in bar # ^^^^^^^^^^ - def visit_match_predicate_node(node) - builder.match_pattern_p( - visit(node.value), - token(node.operator_loc), - within_pattern { |compiler| node.pattern.accept(compiler) } - ) + if RUBY_VERSION >= "3.0" + def visit_match_predicate_node(node) + builder.match_pattern_p( + visit(node.value), + token(node.operator_loc), + within_pattern { |compiler| node.pattern.accept(compiler) } + ) + end + else + def visit_match_predicate_node(node) + builder.match_pattern( + visit(node.value), + token(node.operator_loc), + within_pattern { |compiler| node.pattern.accept(compiler) } + ) + end end # foo => bar diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index d7259c1269e5f1..b74d39c0e3272d 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -89,11 +89,12 @@ module Prism #<%= line %> <%- end -%> <%- end -%> - <%= "private " if field.is_a?(Prism::FlagsField) %>attr_reader :<%= field.name %> + attr_reader :<%= field.name -%><%= "\n private :#{field.name}" if field.is_a?(Prism::FlagsField) %> <%- end -%> # def initialize: (<%= (node.fields.map { |field| "#{field.rbs_class} #{field.name}" } + ["Location location"]).join(", ") %>) -> void def initialize(<%= (node.fields.map(&:name) + ["location"]).join(", ") %>) + @newline = false <%- node.fields.each do |field| -%> @<%= field.name %> = <%= field.name %> <%- end -%> diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index ea5ecb7ccee3ee..f7231b6a4417b9 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -588,43 +588,45 @@ def test_bad_arguments ] end - def test_cannot_assign_to_a_reserved_numbered_parameter - expected = BeginNode( - Location(), - StatementsNode([ - LocalVariableWriteNode(:_1, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_2, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_3, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_4, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_5, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_6, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_7, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_8, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_9, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), - LocalVariableWriteNode(:_10, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()) - ]), - nil, - nil, - nil, - Location() - ) - source = <<~RUBY - begin - _1=:a;_2=:a;_3=:a;_4=:a;_5=:a - _6=:a;_7=:a;_8=:a;_9=:a;_10=:a + if RUBY_VERSION >= "3.0" + def test_cannot_assign_to_a_reserved_numbered_parameter + expected = BeginNode( + Location(), + StatementsNode([ + LocalVariableWriteNode(:_1, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_2, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_3, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_4, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_5, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_6, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_7, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_8, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_9, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()), + LocalVariableWriteNode(:_10, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()) + ]), + nil, + nil, + nil, + Location() + ) + source = <<~RUBY + begin + _1=:a;_2=:a;_3=:a;_4=:a;_5=:a + _6=:a;_7=:a;_8=:a;_9=:a;_10=:a + end + RUBY + assert_errors expected, source, [ + ["_1 is reserved for numbered parameters", 8..10], + ["_2 is reserved for numbered parameters", 14..16], + ["_3 is reserved for numbered parameters", 20..22], + ["_4 is reserved for numbered parameters", 26..28], + ["_5 is reserved for numbered parameters", 32..34], + ["_6 is reserved for numbered parameters", 40..42], + ["_7 is reserved for numbered parameters", 46..48], + ["_8 is reserved for numbered parameters", 52..54], + ["_9 is reserved for numbered parameters", 58..60], + ] end - RUBY - assert_errors expected, source, [ - ["_1 is reserved for numbered parameters", 8..10], - ["_2 is reserved for numbered parameters", 14..16], - ["_3 is reserved for numbered parameters", 20..22], - ["_4 is reserved for numbered parameters", 26..28], - ["_5 is reserved for numbered parameters", 32..34], - ["_6 is reserved for numbered parameters", 40..42], - ["_7 is reserved for numbered parameters", 46..48], - ["_8 is reserved for numbered parameters", 52..54], - ["_9 is reserved for numbered parameters", 58..60], - ] end def test_do_not_allow_trailing_commas_in_method_parameters @@ -1344,23 +1346,25 @@ def test_index_call_with_block_operator_write ] end - def test_writing_numbered_parameter - assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [ - ["_1 is reserved for numbered parameters", 5..7] - ] - end + if RUBY_VERSION >= "3.0" + def test_writing_numbered_parameter + assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [ + ["_1 is reserved for numbered parameters", 5..7] + ] + end - def test_targeting_numbered_parameter - assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [ - ["_1 is reserved for numbered parameters", 5..7] - ] - end + def test_targeting_numbered_parameter + assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [ + ["_1 is reserved for numbered parameters", 5..7] + ] + end - def test_defining_numbered_parameter - error_messages = ["_1 is reserved for numbered parameters"] + def test_defining_numbered_parameter + error_messages = ["_1 is reserved for numbered parameters"] - assert_error_messages "def _1; end", error_messages - assert_error_messages "def self._1; end", error_messages + assert_error_messages "def _1; end", error_messages + assert_error_messages "def self._1; end", error_messages + end end def test_double_scope_numbered_parameters @@ -1409,11 +1413,13 @@ def test_begin_at_toplevel ] end - def test_numbered_parameters_in_block_arguments - source = "foo { |_1| }" - assert_errors expression(source), source, [ - ["_1 is reserved for numbered parameters", 7..9], - ] + if RUBY_VERSION >= "3.0" + def test_numbered_parameters_in_block_arguments + source = "foo { |_1| }" + assert_errors expression(source), source, [ + ["_1 is reserved for numbered parameters", 7..9], + ] + end end def test_conditional_predicate_closed diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index a16de14cbeb802..28b0725d6f0dba 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -43,15 +43,18 @@ def assert_equal_nodes(expected, actual, compare_location: true, parent: nil) ) end when SourceFileNode - deconstructed_expected = expected.deconstruct_keys(nil) - deconstructed_actual = actual.deconstruct_keys(nil) - assert_equal deconstructed_expected.keys, deconstructed_actual.keys + expected_deconstruct = expected.deconstruct_keys(nil) + actual_deconstruct = actual.deconstruct_keys(nil) + assert_equal expected_deconstruct.keys, actual_deconstruct.keys # Filepaths can be different if test suites were run on different # machines. We accommodate for this by comparing the basenames, and not # the absolute filepaths. - assert_equal deconstructed_expected.except(:filepath), deconstructed_actual.except(:filepath) - assert_equal File.basename(deconstructed_expected[:filepath]), File.basename(deconstructed_actual[:filepath]) + expected_filepath = expected_deconstruct.delete(:filepath) + actual_filepath = actual_deconstruct.delete(:filepath) + + assert_equal expected_deconstruct, actual_deconstruct + assert_equal File.basename(expected_filepath), File.basename(actual_filepath) when Node deconstructed_expected = expected.deconstruct_keys(nil) deconstructed_actual = actual.deconstruct_keys(nil) From 0b7f51683446c81d7352d7d2eab9f49bc3bbae58 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 7 Feb 2024 16:57:30 +0000 Subject: [PATCH 031/142] [ruby/irb] Bump version to v1.11.2 (https://github.com/ruby/irb/pull/865) https://github.com/ruby/irb/commit/afe1f459cc --- lib/irb/version.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index f2e0e7d7f16cc8..585c8370208633 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.11.1" + VERSION = "1.11.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-01-08" + @LAST_UPDATE_DATE = "2024-02-07" end From f741b05d1f06a1d82e9cc6ccd722a06bb42c3347 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 7 Feb 2024 16:59:28 +0000 Subject: [PATCH 032/142] Update default gems list at 0b7f51683446c81d7352d7d2eab9f4 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f6d920c7cd2cfd..366d013664f312 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,7 +27,7 @@ The following default gems are updated. * erb 4.0.4 * fiddle 1.1.3 * io-console 0.7.2 -* irb 1.11.1 +* irb 1.11.2 * net-http 0.4.1 * prism 0.21.0 * reline 0.4.2 From b2392c6be418703e8941226ac80b359188bf3c5d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 6 Feb 2024 10:20:55 -0500 Subject: [PATCH 033/142] Fix memory leak when parsing invalid pattern matching If the pattern matching is invalid, then the pvtbl would get leaked. For example: 10.times do 100_000.times do eval(<<~RUBY) case {a: 1} in {"a" => 1} end RUBY rescue SyntaxError end puts `ps -o rss= -p #{$$}` end Before: 28096 44768 61472 78512 94992 111504 128096 144528 161008 177472 After: 14096 14112 14112 14176 14208 14240 14240 14240 14240 14240 --- parse.y | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parse.y b/parse.y index 6836576d800acf..f9d224df34a475 100644 --- a/parse.y +++ b/parse.y @@ -16070,6 +16070,11 @@ rb_ruby_parser_free(void *ptr) } } string_buffer_free(p); + + if (p->pvtbl) { + st_free_table(p->pvtbl); + } + xfree(ptr); } From fcc8df622a47a99c3df9889ec7efaf069ed5fa70 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 7 Feb 2024 13:36:06 -0500 Subject: [PATCH 034/142] Bump prism version --- 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 060ab662a47a6b..0d0d548d35395a 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.21.0" + spec.version = "0.22.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index 8b64e6ef3e7240..ba97583e6449f7 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.21.0" +#define EXPECTED_PRISM_VERSION "0.22.0" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 205cbc45ec03b0..58ec6e85c057e0 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -20,7 +20,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 21 + MINOR_VERSION = 22 # 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 9adf782d8173ac..f53ad4bb2e15e2 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 21 +#define PRISM_VERSION_MINOR 22 /** * 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.21.0" +#define PRISM_VERSION "0.22.0" #endif From a5c871e20105859a2ee286742d9a4e0eb1b2d0be Mon Sep 17 00:00:00 2001 From: git Date: Wed, 7 Feb 2024 18:37:16 +0000 Subject: [PATCH 035/142] Update default gems list at fcc8df622a47a99c3df9889ec7efaf [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 366d013664f312..2245591cc0dfb7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -29,7 +29,7 @@ The following default gems are updated. * io-console 0.7.2 * irb 1.11.2 * net-http 0.4.1 -* prism 0.21.0 +* prism 0.22.0 * reline 0.4.2 * stringio 3.1.1 * strscan 3.0.9 From b1310940e36c14bd07dbc2db885fbd6ec8c35bf8 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 7 Feb 2024 10:22:29 +0000 Subject: [PATCH 036/142] [ruby/prism] RipperCompat: support more kinds of method calls and operators. Add tests. Start parsing some simpler fixture code. https://github.com/ruby/prism/commit/997f4191d8 --- lib/prism/ripper_compat.rb | 103 ++++++++++++++++++++++++++----- test/prism/ripper_compat_test.rb | 45 +++++++++++++- 2 files changed, 132 insertions(+), 16 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 912114cfef5373..98dc762f624335 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -116,32 +116,80 @@ def visit_array_node(node) end # Visit a CallNode node. + # Ripper distinguishes between many different method-call + # nodes -- unary and binary operators, "command" calls with + # no parentheses, and call/fcall/vcall. def visit_call_node(node) if node.variable_call? - if node.message.match?(/^[[:alpha:]_]/) - bounds(node.message_loc) - return on_vcall(on_ident(node.message)) - end + raise NotImplementedError unless node.receiver.nil? - raise NotImplementedError, "Non-alpha variable call" + bounds(node.message_loc) + return on_vcall(on_ident(node.message)) end if node.opening_loc.nil? - left = visit(node.receiver) - if node.arguments&.arguments&.length == 1 - right = visit(node.arguments.arguments.first) - - on_binary(left, node.name, right) - elsif !node.arguments || node.arguments.empty? - on_unary(node.name, left) + # No opening_loc can mean an operator. It can also mean a + # method call with no parentheses. + if node.message.match?(/^[[:punct:]]/) + left = visit(node.receiver) + if node.arguments&.arguments&.length == 1 + right = visit(node.arguments.arguments.first) + + return on_binary(left, node.name, right) + elsif !node.arguments || node.arguments.empty? + return on_unary(node.name, left) + else + raise NotImplementedError, "More than two arguments for operator" + end + elsif node.call_operator_loc.nil? + # In Ripper a method call like "puts myvar" with no parenthesis is a "command" + bounds(node.message_loc) + ident_val = on_ident(node.message) + args = args_node_to_arguments(node.arguments) + return on_command(ident_val, args) else - raise NotImplementedError, "More than two arguments for operator" + operator = node.call_operator_loc.slice + if operator == "." + left_val = visit(node.receiver) + + bounds(node.call_operator_loc) + dot_val = on_period(node.call_operator) + + bounds(node.message_loc) + right_val = on_ident(node.message) + + return on_call(left_val, dot_val, right_val) + else + raise NotImplementedError, "operator other than dot for call: #{operator.inspect}" + end end + end + + # A non-operator method call with parentheses + args = on_arg_paren(args_node_to_arguments(node.arguments)) + + bounds(node.message_loc) + ident_val = on_ident(node.message) + + bounds(node.location) + args_call_val = on_method_add_arg(on_fcall(ident_val), args) + if node.block + raise NotImplementedError, "Method call with a block!" else - raise NotImplementedError, "Non-nil opening_loc" + return args_call_val end end + # Visit an AndNode + def visit_and_node(node) + visit_binary_operator(node) + end + + # Visit an AndNode + def visit_or_node(node) + visit_binary_operator(node) + end + # Visit a FloatNode node. def visit_float_node(node) visit_number(node) { |text| on_float(text) } @@ -203,6 +251,24 @@ def visit_statements_node(node) end end + private + + # Ripper generates an interesting format of argument list. + # We'd like to convert an ArgumentsNode to one. + def args_node_to_arguments(args_node) + return nil if args_node.nil? + + args = on_args_new + args_node.arguments.each do |arg| + bounds(arg.location) + args = on_args_add(args, visit(arg)) + end + + on_args_add_block(args, false) + end + + public + ############################################################################ # Entrypoints for subclasses ############################################################################ @@ -238,13 +304,20 @@ def visit_number(node) value = yield slice[1..-1] bounds(node.location) - on_unary(RUBY_ENGINE == "jruby" ? :- : :-@, value) + on_unary(RUBY_ENGINE == "jruby" && JRUBY_VERSION < "9.4.6.0" ? :- : :-@, value) else bounds(location) yield slice end end + # Visit a binary operator node like an AndNode or OrNode + def visit_binary_operator(node) + left_val = visit(node.left) + right_val = visit(node.right) + on_binary(left_val, node.operator_loc.slice.to_sym, right_val) + end + # This method is responsible for updating lineno and column information # to reflect the current node. # diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index 8ca8545add34d1..ae611e5e46c9ec 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -24,8 +24,30 @@ def test_binary_parens assert_equivalent("(3 + 7) * 4") end - def test_ident + def test_method_calls_with_variable_names assert_equivalent("foo") + assert_equivalent("foo()") + assert_equivalent("foo(-7)") + assert_equivalent("foo(1, 2, 3)") + assert_equivalent("foo 1") + assert_equivalent("foo bar") + assert_equivalent("foo 1, 2") + assert_equivalent("foo.bar") + assert_equivalent("🗻") + assert_equivalent("🗻.location") + assert_equivalent("foo.🗻") + assert_equivalent("🗻.😮!") + assert_equivalent("🗻 🗻,🗻,🗻") + end + + def test_method_calls_on_immediate_values + assert_equivalent("7.even?") + assert_equivalent("!1") + assert_equivalent("7 && 7") + assert_equivalent("7 and 7") + assert_equivalent("7 || 7") + assert_equivalent("7 or 7") + #assert_equivalent("'racecar'.reverse") end def test_range @@ -58,4 +80,25 @@ def assert_equivalent(source) assert_equal expected, RipperCompat.sexp_raw(source) end end + + class RipperCompatFixturesTest < TestCase + #base = File.join(__dir__, "fixtures") + #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base] + relatives = ["arithmetic.txt", "integer_operations.txt"] + + relatives.each do |relative| + define_method "test_ripper_filepath_#{relative}" do + path = File.join(__dir__, "fixtures", relative) + + # First, read the source from the path. Use binmode to avoid converting CRLF on Windows, + # and explicitly set the external encoding to UTF-8 to override the binmode default. + source = File.read(path, binmode: true, external_encoding: Encoding::UTF_8) + + expected = Ripper.sexp_raw(source) + refute_nil expected + assert_equal expected, RipperCompat.sexp_raw(source) + end + end + + end end From 73d222e1efa64b82bdd23efdb73fa39031fe0b9c Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 7 Feb 2024 11:05:01 +0000 Subject: [PATCH 037/142] [ruby/prism] Support &. calls and calling with blocks, test with fixtures https://github.com/ruby/prism/commit/e346fa583a --- lib/prism/ripper_compat.rb | 47 +++++++++++++++++++++++++------- test/prism/ripper_compat_test.rb | 13 ++++++++- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 98dc762f624335..764b4f3708cc8d 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -142,31 +142,47 @@ def visit_call_node(node) raise NotImplementedError, "More than two arguments for operator" end elsif node.call_operator_loc.nil? - # In Ripper a method call like "puts myvar" with no parenthesis is a "command" + # In Ripper a method call like "puts myvar" with no parenthesis is a "command". bounds(node.message_loc) ident_val = on_ident(node.message) - args = args_node_to_arguments(node.arguments) - return on_command(ident_val, args) + args = node.arguments.nil? ? nil : args_node_to_arguments(node.arguments) + + # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") + if node.block + block_val = visit(node.block) + # In these calls, even if node.arguments is nil, we still get an :args_new call. + method_args_val = on_method_add_arg(on_fcall(ident_val), args_node_to_arguments(node.arguments)) + return on_method_add_block(method_args_val, on_brace_block(nil, block_val)) + else + return on_command(ident_val, args) + end else operator = node.call_operator_loc.slice - if operator == "." + if operator == "." || operator == "&." left_val = visit(node.receiver) bounds(node.call_operator_loc) - dot_val = on_period(node.call_operator) + operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) bounds(node.message_loc) right_val = on_ident(node.message) - return on_call(left_val, dot_val, right_val) + call_val = on_call(left_val, operator_val, right_val) + + if node.block + block_val = visit(node.block) + return on_method_add_block(call_val, on_brace_block(nil, block_val)) + else + return call_val + end else - raise NotImplementedError, "operator other than dot for call: #{operator.inspect}" + raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" end end end # A non-operator method call with parentheses - args = on_arg_paren(args_node_to_arguments(node.arguments)) + args = on_arg_paren(node.arguments.nil? ? nil : args_node_to_arguments(node.arguments)) bounds(node.message_loc) ident_val = on_ident(node.message) @@ -174,12 +190,23 @@ def visit_call_node(node) bounds(node.location) args_call_val = on_method_add_arg(on_fcall(ident_val), args) if node.block - raise NotImplementedError, "Method call with a block!" + block_val = visit(node.block) + + return on_method_add_block(args_call_val, on_brace_block(nil, block_val)) else return args_call_val end end + # Visit a BlockNode + def visit_block_node(node) + if node.body.nil? + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.body) + end + end + # Visit an AndNode def visit_and_node(node) visit_binary_operator(node) @@ -256,7 +283,7 @@ def visit_statements_node(node) # Ripper generates an interesting format of argument list. # We'd like to convert an ArgumentsNode to one. def args_node_to_arguments(args_node) - return nil if args_node.nil? + return on_args_new if args_node.nil? args = on_args_new args_node.arguments.each do |arg| diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index ae611e5e46c9ec..8c54f59ef7a563 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -38,6 +38,10 @@ def test_method_calls_with_variable_names assert_equivalent("foo.🗻") assert_equivalent("🗻.😮!") assert_equivalent("🗻 🗻,🗻,🗻") + assert_equivalent("foo&.bar") + assert_equivalent("foo { bar }") + assert_equivalent("foo.bar { 7 }") + assert_equivalent("foo(1) { bar }") end def test_method_calls_on_immediate_values @@ -84,7 +88,11 @@ def assert_equivalent(source) class RipperCompatFixturesTest < TestCase #base = File.join(__dir__, "fixtures") #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base] - relatives = ["arithmetic.txt", "integer_operations.txt"] + relatives = [ + "arithmetic.txt", + "comments.txt", + "integer_operations.txt", + ] relatives.each do |relative| define_method "test_ripper_filepath_#{relative}" do @@ -95,6 +103,9 @@ class RipperCompatFixturesTest < TestCase source = File.read(path, binmode: true, external_encoding: Encoding::UTF_8) expected = Ripper.sexp_raw(source) + if expected.nil? + puts "Could not parse #{path.inspect}!" + end refute_nil expected assert_equal expected, RipperCompat.sexp_raw(source) end From 5b7baa04862906918bb010d8f4de07d7f272f254 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 7 Feb 2024 13:45:43 +0000 Subject: [PATCH 038/142] [ruby/prism] More different block-call syntaxes, support more types of method calls https://github.com/ruby/prism/commit/40cf114a24 --- lib/prism/ripper_compat.rb | 152 ++++++++++++++++--------------- test/prism/ripper_compat_test.rb | 20 +++- 2 files changed, 96 insertions(+), 76 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 764b4f3708cc8d..f25ec872f1fea9 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -128,57 +128,7 @@ def visit_call_node(node) end if node.opening_loc.nil? - # No opening_loc can mean an operator. It can also mean a - # method call with no parentheses. - if node.message.match?(/^[[:punct:]]/) - left = visit(node.receiver) - if node.arguments&.arguments&.length == 1 - right = visit(node.arguments.arguments.first) - - return on_binary(left, node.name, right) - elsif !node.arguments || node.arguments.empty? - return on_unary(node.name, left) - else - raise NotImplementedError, "More than two arguments for operator" - end - elsif node.call_operator_loc.nil? - # In Ripper a method call like "puts myvar" with no parenthesis is a "command". - bounds(node.message_loc) - ident_val = on_ident(node.message) - args = node.arguments.nil? ? nil : args_node_to_arguments(node.arguments) - - # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") - if node.block - block_val = visit(node.block) - # In these calls, even if node.arguments is nil, we still get an :args_new call. - method_args_val = on_method_add_arg(on_fcall(ident_val), args_node_to_arguments(node.arguments)) - return on_method_add_block(method_args_val, on_brace_block(nil, block_val)) - else - return on_command(ident_val, args) - end - else - operator = node.call_operator_loc.slice - if operator == "." || operator == "&." - left_val = visit(node.receiver) - - bounds(node.call_operator_loc) - operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) - - bounds(node.message_loc) - right_val = on_ident(node.message) - - call_val = on_call(left_val, operator_val, right_val) - - if node.block - block_val = visit(node.block) - return on_method_add_block(call_val, on_brace_block(nil, block_val)) - else - return call_val - end - else - raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" - end - end + return visit_no_paren_call(node) end # A non-operator method call with parentheses @@ -212,7 +162,7 @@ def visit_and_node(node) visit_binary_operator(node) end - # Visit an AndNode + # Visit an OrNode def visit_or_node(node) visit_binary_operator(node) end @@ -278,24 +228,6 @@ def visit_statements_node(node) end end - private - - # Ripper generates an interesting format of argument list. - # We'd like to convert an ArgumentsNode to one. - def args_node_to_arguments(args_node) - return on_args_new if args_node.nil? - - args = on_args_new - args_node.arguments.each do |arg| - bounds(arg.location) - args = on_args_add(args, visit(arg)) - end - - on_args_add_block(args, false) - end - - public - ############################################################################ # Entrypoints for subclasses ############################################################################ @@ -312,6 +244,72 @@ def self.sexp(source) private + # Generate Ripper events for a CallNode with no opening_loc + def visit_no_paren_call(node) + # No opening_loc can mean an operator. It can also mean a + # method call with no parentheses. + if node.message.match?(/^[[:punct:]]/) + left = visit(node.receiver) + if node.arguments&.arguments&.length == 1 + right = visit(node.arguments.arguments.first) + + return on_binary(left, node.name, right) + elsif !node.arguments || node.arguments.empty? + return on_unary(node.name, left) + else + raise NotImplementedError, "More than two arguments for operator" + end + elsif node.call_operator_loc.nil? + # In Ripper a method call like "puts myvar" with no parenthesis is a "command". + bounds(node.message_loc) + ident_val = on_ident(node.message) + + # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") + if node.block + block_val = visit(node.block) + # In these calls, even if node.arguments is nil, we still get an :args_new call. + method_args_val = on_method_add_arg(on_fcall(ident_val), args_node_to_arguments(node.arguments)) + return on_method_add_block(method_args_val, on_brace_block(nil, block_val)) + else + args = node.arguments.nil? ? nil : args_node_to_arguments(node.arguments) + return on_command(ident_val, args) + end + else + operator = node.call_operator_loc.slice + if operator == "." || operator == "&." + left_val = visit(node.receiver) + + bounds(node.call_operator_loc) + operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) + + bounds(node.message_loc) + right_val = on_ident(node.message) + + call_val = on_call(left_val, operator_val, right_val) + + if node.block + block_val = visit(node.block) + return on_method_add_block(call_val, on_brace_block(nil, block_val)) + else + return call_val + end + else + raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" + end + end + end + + # Ripper generates an interesting format of argument list. + # It seems to be very location-specific. We should get rid of + # this method and make it clearer how it's done in each place. + def args_node_to_arguments(args_node) + return on_args_new if args_node.nil? + + args = visit_elements(args_node.arguments) + + on_args_add_block(args, false) + end + # Visit a list of elements, like the elements of an array or arguments. def visit_elements(elements) bounds(elements.first.location) @@ -331,13 +329,25 @@ def visit_number(node) value = yield slice[1..-1] bounds(node.location) - on_unary(RUBY_ENGINE == "jruby" && JRUBY_VERSION < "9.4.6.0" ? :- : :-@, value) + on_unary(visit_unary_operator(:-@), value) else bounds(location) yield slice end end + if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0") + # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@ + def visit_unary_operator(value) + value == :-@ ? :- : value + end + else + # For most Rubies and JRuby after 9.4.6.0 this is a no-op. + def visit_unary_operator(value) + value + end + end + # Visit a binary operator node like an AndNode or OrNode def visit_binary_operator(node) left_val = visit(node.left) diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index 8c54f59ef7a563..1aaade046f2df0 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -33,15 +33,25 @@ def test_method_calls_with_variable_names assert_equivalent("foo bar") assert_equivalent("foo 1, 2") assert_equivalent("foo.bar") - assert_equivalent("🗻") - assert_equivalent("🗻.location") - assert_equivalent("foo.🗻") - assert_equivalent("🗻.😮!") - assert_equivalent("🗻 🗻,🗻,🗻") + + # TruffleRuby prints emoji symbols differently in a way that breaks here. + if RUBY_ENGINE != "truffleruby" + assert_equivalent("🗻") + assert_equivalent("🗻.location") + assert_equivalent("foo.🗻") + assert_equivalent("🗻.😮!") + assert_equivalent("🗻 🗻,🗻,🗻") + end + assert_equivalent("foo&.bar") assert_equivalent("foo { bar }") assert_equivalent("foo.bar { 7 }") assert_equivalent("foo(1) { bar }") + assert_equivalent("foo(bar)") + assert_equivalent("foo(bar(1))") + assert_equivalent("foo bar(1)") + # assert_equivalent("foo(bar 1)") # This succeeds for me locally but fails on CI + # assert_equivalent("foo bar 1") end def test_method_calls_on_immediate_values From 1b68b459caa472eb85cfef55bf5cd200da9bf3c5 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 7 Feb 2024 18:39:45 +0000 Subject: [PATCH 039/142] [ruby/prism] Commit Kevin's suggestion to simplify grabbing the operator. https://github.com/ruby/prism/commit/874ba7a1f4 Co-Authored-By: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index f25ec872f1fea9..44983f8596ccb3 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -352,7 +352,7 @@ def visit_unary_operator(value) def visit_binary_operator(node) left_val = visit(node.left) right_val = visit(node.right) - on_binary(left_val, node.operator_loc.slice.to_sym, right_val) + on_binary(left_val, node.operator.to_sym, right_val) end # This method is responsible for updating lineno and column information From 0e1f22ac7ee5719048471aad29f69221d045bd34 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 7 Feb 2024 15:50:58 -0500 Subject: [PATCH 040/142] [ruby/prism] Dev-only CLI We keep adding more scripts to /bin that are doing the same kinds of processing. Instead, this commit consolidates them all into a single CLI that shares the same logic so that we can consistently read files in the same way. It keeps around 2 binstubs for bin/lex and bin/parse since those are the most used and I'm sure people have built up muscle memory for those. Those scripts are now just wrappers for forwarding to bin/prism. https://github.com/ruby/prism/commit/bddcb9bf17 --- lib/prism/lex_compat.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 0cc63cd94481b2..c11903423d9276 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "delegate" +require "ripper" module Prism # This class is responsible for lexing the source using prism and then From 04d42650d91690b9f929d4c765d5b114e111d4ce Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 7 Feb 2024 16:36:22 -0800 Subject: [PATCH 041/142] Remove obsoleted rb_vm_opt_cfunc_p That was added for MJIT's inlining decisions, but we no longer have MJIT. --- vm_insnhelper.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index ac283002572c23..a74218427868eb 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6556,22 +6556,6 @@ vm_trace_hook(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VAL } } -// Return true if given cc has cfunc which is NOT handled by opt_send_without_block. -bool -rb_vm_opt_cfunc_p(CALL_CACHE cc, int insn) -{ - switch (insn) { - case BIN(opt_eq): - return check_cfunc(vm_cc_cme(cc), rb_obj_equal); - case BIN(opt_nil_p): - return check_cfunc(vm_cc_cme(cc), rb_false); - case BIN(opt_not): - return check_cfunc(vm_cc_cme(cc), rb_obj_not); - default: - return false; - } -} - #define VM_TRACE_HOOK(target_event, val) do { \ if ((pc_events & (target_event)) & enabled_flags) { \ vm_trace_hook(ec, reg_cfp, pc, pc_events, (target_event), global_hooks, local_hooks_ptr, (val)); \ From 9f1afefaa8cf84e083bb1c281050f05c26e54bc1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Feb 2024 11:02:48 +0900 Subject: [PATCH 042/142] Now we can use ruby analysis with large runner --- .github/workflows/codeql-analysis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index be4d2e25942cfd..c4278cbe1bef7d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -56,10 +56,9 @@ jobs: include: - language: cpp os: ubuntu-latest - ram: 8192 + # ruby analysis used large memory. We need to use a larger runner. - language: ruby os: macos-arm-oss - ram: 13312 steps: - name: Checkout repository @@ -93,10 +92,6 @@ jobs: category: '/language:${{ matrix.language }}' upload: False output: sarif-results - ram: ${{ matrix.ram }} - # CodeQL randomly hits `OutOfMemoryError "Java heap space"`. - # GitHub recommends running a larger runner to fix it, but we don't pay for it. - continue-on-error: true - name: filter-sarif uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1 From ce6054a667029af3edc32cfd2e0e7c281cfdf332 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Feb 2024 11:15:41 +0900 Subject: [PATCH 043/142] Move an embedded directive outside macro arguments Suppress warnings/errors by -Wembedded-directive with `-std=c99` on macOS. --- addr2line.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addr2line.c b/addr2line.c index 2a69dd0966d922..02a3e617a6b80f 100644 --- a/addr2line.c +++ b/addr2line.c @@ -2384,13 +2384,14 @@ fill_lines(int num_traces, void **traces, int check_debuglink, goto fail; } else { - kprintf("'%s' is not a " # ifdef __LP64__ - "64" +# define bitsize "64" # else - "32" +# define bitsize "32" # endif + kprintf("'%s' is not a " bitsize "-bit Mach-O file!\n",binary_filename); +# undef bitsize close(fd); goto fail; } From e1834cdfe0d95692e597bbaa9bb474b7680efe00 Mon Sep 17 00:00:00 2001 From: Masato Nakamura Date: Tue, 30 Jan 2024 06:40:53 +0900 Subject: [PATCH 044/142] [ruby/fiddle] Set changelog_uri gem metadata (https://github.com/ruby/fiddle/pull/138) See https://guides.rubygems.org/specification-reference/#metadata for changelog_uri metadata. https://github.com/ruby/fiddle/commit/0bd8e96adc --- ext/fiddle/fiddle.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/fiddle/fiddle.gemspec b/ext/fiddle/fiddle.gemspec index 878109395bf77c..3a1072dd49975e 100644 --- a/ext/fiddle/fiddle.gemspec +++ b/ext/fiddle/fiddle.gemspec @@ -56,4 +56,5 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.5.0" spec.metadata["msys2_mingw_dependencies"] = "libffi" + spec.metadata["changelog_uri"] = "https://github.com/ruby/fiddle/releases" end From 5afae77ce9bd7352e194d8603ebe35b3679207b3 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 21 Jan 2024 11:45:08 +0900 Subject: [PATCH 045/142] [ruby/strscan] Bump version https://github.com/ruby/strscan/commit/842845af1f --- ext/strscan/strscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 4598d13c9096be..0400089a182a16 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.0.9" +#define STRSCAN_VERSION "3.1.0" /* ======================================================================= Data Type Definitions From 39f2e37ff1c12cf4c9fec0b697a1495bc1930995 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Sat, 3 Feb 2024 11:56:20 +0100 Subject: [PATCH 046/142] [ruby/strscan] Don't add begin to length for new string slice (https://github.com/ruby/strscan/pull/87) Fixes https://github.com/ruby/strscan/pull/86 https://github.com/ruby/strscan/commit/c17b015c00 --- test/strscan/test_stringscanner.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb index d15c1d85684897..2a127a773a3698 100644 --- a/test/strscan/test_stringscanner.rb +++ b/test/strscan/test_stringscanner.rb @@ -841,4 +841,12 @@ def test_skip_with_begenning_of_line_anchor_not_match assert_equal 1, s.skip(/a/) assert_nil s.skip(/^b/) end + + # ruby/strscan#86 + def test_scan_shared_string + s = "hellohello"[5..-1] + ss = StringScanner.new(s).scan(/hello/) + + assert_equal "hello", ss + end end From ce2618c628e017f6d83576d95a294a6247e30b19 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 4 Feb 2024 15:45:45 +0900 Subject: [PATCH 047/142] [ruby/strscan] Bump version https://github.com/ruby/strscan/commit/ba338b882c --- ext/strscan/strscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 0400089a182a16..bed1c87cdc46a2 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.0" +#define STRSCAN_VERSION "3.1.1" /* ======================================================================= Data Type Definitions From debc5aaee57a25745259fd02a54701e6c184fa7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 9 Oct 2023 21:07:15 +0200 Subject: [PATCH 048/142] [rubygems/rubygems] Remove unused parameter https://github.com/rubygems/rubygems/commit/085eda7147 --- lib/bundler/installer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index e837f732cf9376..5245da2e0e6525 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -260,8 +260,8 @@ def resolve_if_needed(options) true end - def lock(opts = {}) - @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + def lock + @definition.lock(Bundler.default_lockfile) end end end From 24d5e7176e80cecfc38daf80020fb85f1144083b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 2 Mar 2023 20:18:35 +0100 Subject: [PATCH 049/142] [rubygems/rubygems] Refactor lockfile generation https://github.com/rubygems/rubygems/commit/6a0c03c77f --- lib/bundler.rb | 5 +- lib/bundler/cli/lock.rb | 9 +-- lib/bundler/definition.rb | 82 ++++++++++++++++--------- lib/bundler/injector.rb | 2 +- lib/bundler/installer.rb | 2 +- lib/bundler/runtime.rb | 2 +- spec/bundler/bundler/definition_spec.rb | 12 ++-- 7 files changed, 70 insertions(+), 44 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index 7bb6690e5241f5..59a1107bb71d01 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -200,12 +200,13 @@ def environment # # @param unlock [Hash, Boolean, nil] Gems that have been requested # to be updated or true if all gems should be updated + # @param lockfile [Pathname] Path to Gemfile.lock # @return [Bundler::Definition] - def definition(unlock = nil) + def definition(unlock = nil, lockfile = default_lockfile) @definition = nil if unlock @definition ||= begin configure - Definition.build(default_gemfile, default_lockfile, unlock) + Definition.build(default_gemfile, lockfile, unlock) end end diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index 7247121df551c4..dac3d2a09a91b1 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -33,8 +33,11 @@ def run update = { bundler: bundler } end + file = options[:lockfile] + file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile + Bundler.settings.temporary(frozen: false) do - definition = Bundler.definition(update) + definition = Bundler.definition(update, file) Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update] @@ -60,10 +63,8 @@ def run if print puts definition.to_lock else - file = options[:lockfile] - file = file ? File.expand_path(file) : Bundler.default_lockfile puts "Writing lockfile to #{file}" - definition.lock(file) + definition.lock end end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 0b0e63f77e5e4c..32a70899254241 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -320,38 +320,24 @@ def groups dependencies.map(&:groups).flatten.uniq end - def lock(file, preserve_unknown_sections = false) - return if Definition.no_lock - - contents = to_lock - - # Convert to \r\n if the existing lock has them - # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") - - if @locked_bundler_version - locked_major = @locked_bundler_version.segments.first - current_major = bundler_version_to_lock.segments.first - - updating_major = locked_major < current_major - end - - preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false) + if [true, false, nil].include?(file_or_preserve_unknown_sections) + target_lockfile = lockfile || Bundler.default_lockfile + preserve_unknown_sections = file_or_preserve_unknown_sections + else + target_lockfile = file_or_preserve_unknown_sections + preserve_unknown_sections = preserve_unknown_sections_or_unused - if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) - return if Bundler.frozen_bundle? - SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } - return - end + suggestion = if target_lockfile == lockfile + "To fix this warning, remove it from the `Definition#lock` call." + else + "Instead, instantiate a new definition passing `#{target_lockfile}`, and call `lock` without a file argument on that definition" + end - if Bundler.frozen_bundle? - Bundler.ui.error "Cannot write a changed lockfile while frozen." - return + warn "Passing a file to `Definition#lock` is deprecated. #{suggestion}" end - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } - end + write_lock(target_lockfile, preserve_unknown_sections) end def locked_ruby_version @@ -518,7 +504,45 @@ def should_add_extra_platforms? end def lockfile_exists? - lockfile && File.exist?(lockfile) + file_exists?(lockfile) + end + + def file_exists?(file) + file && File.exist?(file) + end + + def write_lock(file, preserve_unknown_sections) + return if Definition.no_lock + + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = bundler_version_to_lock.segments.first + + updating_major = locked_major < current_major + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + if file_exists?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + return if Bundler.frozen_bundle? + SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } + return + end + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end end def resolver diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index 2cf7754ecb5335..cf561c2ee49de3 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -50,7 +50,7 @@ def inject(gemfile_path, lockfile_path) append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any? # since we resolved successfully, write out the lockfile - @definition.lock(Bundler.default_lockfile) + @definition.lock # invalidate the cached Bundler.definition Bundler.reset_paths! diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 5245da2e0e6525..018324f8402e8e 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -261,7 +261,7 @@ def resolve_if_needed(options) end def lock - @definition.lock(Bundler.default_lockfile) + @definition.lock end end end diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 993f1082c3382e..ec772cfe7b2e77 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -95,7 +95,7 @@ def self.definition_method(meth) def lock(opts = {}) return if @definition.no_resolve_needed? - @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + @definition.lock(opts[:preserve_unknown_sections]) end alias_method :gems, :specs diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index a19cf789ee9bb6..85f13d287c2c6a 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -10,34 +10,34 @@ allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end context "when it's not possible to write to the file" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original expect(File).to receive(:open).with("Gemfile.lock", "wb"). and_raise(Errno::EACCES) - expect { subject.lock("Gemfile.lock") }. + expect { subject.lock }. to raise_error(Bundler::PermissionError, /Gemfile\.lock/) end end context "when a temporary resource access issue occurs" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } it "raises a TemporaryResourceError with explanation" do allow(File).to receive(:open).and_call_original expect(File).to receive(:open).with("Gemfile.lock", "wb"). and_raise(Errno::EAGAIN) - expect { subject.lock("Gemfile.lock") }. + expect { subject.lock }. to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/) end end context "when Bundler::Definition.no_lock is set to true" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } before { Bundler::Definition.no_lock = true } after { Bundler::Definition.no_lock = false } it "does not create a lock file" do - subject.lock("Gemfile.lock") + subject.lock expect(File.file?("Gemfile.lock")).to eq false end end From e04120772be6162499023f7245434cb5c9175344 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Fri, 26 Jan 2024 09:11:47 +0100 Subject: [PATCH 050/142] [rubygems/rubygems] Move `subject` to top level context https://github.com/rubygems/rubygems/commit/331c415af0 --- spec/bundler/bundler/definition_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 85f13d287c2c6a..5e66e012c084d6 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -9,9 +9,10 @@ allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end - context "when it's not possible to write to the file" do - subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } + + context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original expect(File).to receive(:open).with("Gemfile.lock", "wb"). @@ -21,8 +22,6 @@ end end context "when a temporary resource access issue occurs" do - subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } - it "raises a TemporaryResourceError with explanation" do allow(File).to receive(:open).and_call_original expect(File).to receive(:open).with("Gemfile.lock", "wb"). @@ -32,7 +31,6 @@ end end context "when Bundler::Definition.no_lock is set to true" do - subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } before { Bundler::Definition.no_lock = true } after { Bundler::Definition.no_lock = false } From 5500f880f329cc8593d3b234577994c1605b8ba4 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Fri, 26 Jan 2024 09:21:24 +0100 Subject: [PATCH 051/142] [rubygems/rubygems] Fix incorrect 4th parameter to Definition.new https://github.com/rubygems/rubygems/commit/54948e428d --- spec/bundler/bundler/definition_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 5e66e012c084d6..c4a0f0cdb8d479 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -10,7 +10,7 @@ allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end - subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, {}) } context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do From 0c71fb4b865d1902bd3eed2265d988e4e3fda926 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Fri, 26 Jan 2024 09:26:31 +0100 Subject: [PATCH 052/142] [rubygems/rubygems] Run definition specs in an isolated location And consistently pass Pathname's to `Definition.new` like production code does. https://github.com/rubygems/rubygems/commit/660def5b68 --- spec/bundler/bundler/definition_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index c4a0f0cdb8d479..7bc48fce05c8dc 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -5,17 +5,16 @@ RSpec.describe Bundler::Definition do describe "#lock" do before do - allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") } - allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile } allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end - subject { Bundler::Definition.new("Gemfile.lock", [], Bundler::SourceList.new, {}) } + subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) } context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). + expect(File).to receive(:open).with(bundled_app_lock, "wb"). and_raise(Errno::EACCES) expect { subject.lock }. to raise_error(Bundler::PermissionError, /Gemfile\.lock/) @@ -24,7 +23,7 @@ context "when a temporary resource access issue occurs" do it "raises a TemporaryResourceError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). + expect(File).to receive(:open).with(bundled_app_lock, "wb"). and_raise(Errno::EAGAIN) expect { subject.lock }. to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/) From c236212600ff3094fea3b138ee0ff98652d94345 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Fri, 26 Jan 2024 10:41:00 +0100 Subject: [PATCH 053/142] [rubygems/rubygems] Use deprecation helper for deprecation warning https://github.com/rubygems/rubygems/commit/d1963bf1a6 --- lib/bundler/definition.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 32a70899254241..c8faf77b3bfd4e 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -334,7 +334,9 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or "Instead, instantiate a new definition passing `#{target_lockfile}`, and call `lock` without a file argument on that definition" end - warn "Passing a file to `Definition#lock` is deprecated. #{suggestion}" + msg = "`Definition#lock` was passed a target file argument. #{suggestion}" + + Bundler::SharedHelpers.major_deprecation 2, msg end write_lock(target_lockfile, preserve_unknown_sections) From a35cade79125d2ae99cea01914939d27e0c846a2 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Fri, 26 Jan 2024 20:37:22 +0100 Subject: [PATCH 054/142] [rubygems/rubygems] Improve assertion https://github.com/rubygems/rubygems/commit/7f2f2b898c Co-authored-by: Martin Emde --- spec/bundler/bundler/definition_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 7bc48fce05c8dc..28c04e086003d5 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -35,7 +35,7 @@ it "does not create a lock file" do subject.lock - expect(File.file?("Gemfile.lock")).to eq false + expect(bundled_app_lock).not_to be_file end end end From 43af20602e4baa49ebef71aa68dc8d57477bcf61 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 8 Feb 2024 06:07:33 +0000 Subject: [PATCH 055/142] Update default gems list at a35cade79125d2ae99cea01914939d [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2245591cc0dfb7..b95b9d1bb35df2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,7 +32,7 @@ The following default gems are updated. * prism 0.22.0 * reline 0.4.2 * stringio 3.1.1 -* strscan 3.0.9 +* strscan 3.1.1 The following bundled gems are updated. From 75c5e1a1369007adb025ed52826ffe690999377b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Feb 2024 13:51:40 +0900 Subject: [PATCH 056/142] [rubygems/rubygems] Removed unnecessary disabling of Style/RedundantParentheses https://github.com/rubygems/rubygems/commit/2361527c45 --- lib/bundler/rubygems_ext.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 3cb4e70a56a971..e0582beba286f5 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -282,7 +282,7 @@ def match_platforms?(platform, platforms) # On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory. class BasicSpecification - if /^universal\.(?.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM) # rubocop:disable Style/RedundantParentheses + if /^universal\.(?.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM) local_platform = Platform.local if local_platform.cpu == "universal" ORIGINAL_LOCAL_PLATFORM = local_platform.to_s.freeze From 70bb9cf065973e262325df3187471275b6e91698 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Feb 2024 14:00:02 +0900 Subject: [PATCH 057/142] [rubygems/rubygems] rake vendor:install https://github.com/rubygems/rubygems/commit/c38a96ceae --- .../vendor/pub_grub/lib/pub_grub/static_package_source.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb index 4bf61461b269f9..36ab06254d6e91 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb @@ -1,4 +1,5 @@ require_relative 'package' +require_relative 'rubygems' require_relative 'version_constraint' require_relative 'incompatibility' require_relative 'basic_package_source' From 908cedf7032bb753287256aff8a95579aad29226 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 8 Feb 2024 15:29:16 +0900 Subject: [PATCH 058/142] Removed accidentally commit for lockfile --- tool/bundler/vendor_gems.rb.lock | 71 -------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 tool/bundler/vendor_gems.rb.lock diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock deleted file mode 100644 index ad3602c9842c5e..00000000000000 --- a/tool/bundler/vendor_gems.rb.lock +++ /dev/null @@ -1,71 +0,0 @@ -GIT - remote: https://github.com/cocoapods/molinillo.git - revision: 6bc3d6045edadf800ba1b634fef15d3574369e60 - specs: - molinillo (0.8.0) - -GIT - remote: https://github.com/jhawthorn/pub_grub.git - revision: 4250c533895080c356407d1f49619cb90fa2562d - specs: - pub_grub (0.5.0) - -GEM - remote: https://rubygems.org/ - specs: - connection_pool (2.4.1) - fileutils (1.7.2) - net-http (0.4.0) - uri - net-http-persistent (4.0.2) - connection_pool (~> 2.2) - net-protocol (0.2.2) - timeout - optparse (0.4.0) - resolv (0.3.0) - thor (1.3.0) - timeout (0.4.1) - tsort (0.2.0) - uri (0.13.0) - -PLATFORMS - java - ruby - universal-java-11 - universal-java-18 - universal-java-19 - x64-mingw-ucrt - x64-mingw32 - x86_64-darwin-20 - x86_64-linux - -DEPENDENCIES - fileutils (= 1.7.2) - molinillo! - net-http (= 0.4.0) - net-http-persistent (= 4.0.2) - net-protocol (= 0.2.2) - optparse (= 0.4.0) - pub_grub! - resolv (= 0.3.0) - thor (= 1.3.0) - timeout (= 0.4.1) - tsort (= 0.2.0) - -CHECKSUMS - connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 - fileutils (1.7.2) sha256=36a0fb324218263e52b486ad7408e9a295378fe8edc9fd343709e523c0980631 - molinillo (0.8.0) - net-http (0.4.0) sha256=d87a6163ce3c64008bc8764e210d5f4ec9b87ca558a9052eb390b2c2c277f157 - net-http-persistent (4.0.2) sha256=03f827a33857b1d56b4e796957ad19bf5b58367d853fd0a224eb70fba8d02a44 - net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 - optparse (0.4.0) sha256=f584afc034f610ea7b28a9b1a68b0917d34e0da73c40c2b29cd7151c5eb0bade - pub_grub (0.5.0) - resolv (0.3.0) sha256=14b917f1bb4f363c81601295b68097bf1ff8b3c4179972c2d174ffb7e997a406 - thor (1.3.0) sha256=1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3 - timeout (0.4.1) sha256=6f1f4edd4bca28cffa59501733a94215407c6960bd2107331f0280d4abdebb9a - tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f - uri (0.13.0) sha256=26553c2a9399762e1e8bebd4444b4361c4b21298cf1c864b22eeabc9c4998f24 - -BUNDLED WITH - 2.5.0.dev From 482b82ae15f4edd85df4b8043622839cbab077a2 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 8 Feb 2024 16:19:31 +0900 Subject: [PATCH 059/142] Bump typeprof to 0.21.10 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index fcdc6c20ee3f51..c8951f71611cb9 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -18,7 +18,7 @@ net-smtp 0.4.0.1 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime rbs 3.4.3 https://github.com/ruby/rbs -typeprof 0.21.9 https://github.com/ruby/typeprof +typeprof 0.21.10 https://github.com/ruby/typeprof debug 1.9.1 https://github.com/ruby/debug 91fe870eeceb9ffbbc7f1bb4673f9e2f6a2c1f60 racc 1.7.3 https://github.com/ruby/racc mutex_m 0.2.0 https://github.com/ruby/mutex_m From 0292d1b7c34fbee60230623195d1b44a23dd4252 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 8 Feb 2024 08:28:37 +0000 Subject: [PATCH 060/142] Update bundled gems list as of 2024-02-07 --- NEWS.md | 6 +++--- gems/bundled_gems | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index b95b9d1bb35df2..2ef33a42c3767e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -36,12 +36,12 @@ The following default gems are updated. The following bundled gems are updated. -* minitest 5.21.2 +* minitest 5.22.2 * net-ftp 0.3.4 -* net-imap 0.4.9.1 +* net-imap 0.4.10 * net-smtp 0.4.0.1 * rbs 3.4.3 -* typeprof 0.21.9 +* typeprof 0.21.10 * debug 1.9.1 The following bundled gems are promoted from default gems. diff --git a/gems/bundled_gems b/gems/bundled_gems index c8951f71611cb9..b914fbc084ffdf 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -5,14 +5,14 @@ # - repository-url: URL from where clone for test # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.21.2 https://github.com/minitest/minitest +minitest 5.22.2 https://github.com/minitest/minitest power_assert 2.0.3 https://github.com/ruby/power_assert rake 13.1.0 https://github.com/ruby/rake test-unit 3.6.1 https://github.com/test-unit/test-unit rexml 3.2.6 https://github.com/ruby/rexml rss 0.3.0 https://github.com/ruby/rss net-ftp 0.3.4 https://github.com/ruby/net-ftp -net-imap 0.4.9.1 https://github.com/ruby/net-imap +net-imap 0.4.10 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.4.0.1 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix From d31a12a210bec646eadc23c11ede29f05e72e373 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Jan 2024 15:09:51 +0900 Subject: [PATCH 061/142] Optional detail info at assertion failure --- error.c | 15 +++++++ include/ruby/assert.h | 91 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/error.c b/error.c index 16006b483ffbc5..385da82f3865f0 100644 --- a/error.c +++ b/error.c @@ -1122,11 +1122,26 @@ rb_report_bug_valist(VALUE file, int line, const char *fmt, va_list args) void rb_assert_failure(const char *file, int line, const char *name, const char *expr) +{ + rb_assert_failure_detail(file, line, name, expr, NULL); +} + +void +rb_assert_failure_detail(const char *file, int line, const char *name, const char *expr, + const char *fmt, ...) { FILE *out = stderr; fprintf(out, "Assertion Failed: %s:%d:", file, line); if (name) fprintf(out, "%s:", name); fprintf(out, "%s\n%s\n\n", expr, rb_dynamic_description); + + if (fmt && *fmt) { + va_list args; + va_start(args, fmt); + vfprintf(out, fmt, args); + va_end(args); + } + preface_dump(out); rb_vm_bugreport(NULL, out); bug_report_end(out, -1); diff --git a/include/ruby/assert.h b/include/ruby/assert.h index 0c052363bcf79a..ebeae3e7be547f 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -22,6 +22,7 @@ */ #include "ruby/internal/assume.h" #include "ruby/internal/attr/cold.h" +#include "ruby/internal/attr/format.h" #include "ruby/internal/attr/noreturn.h" #include "ruby/internal/cast.h" #include "ruby/internal/dllexport.h" @@ -132,6 +133,11 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() RBIMPL_ATTR_NORETURN() RBIMPL_ATTR_COLD() void rb_assert_failure(const char *file, int line, const char *name, const char *expr); + +RBIMPL_ATTR_NORETURN() +RBIMPL_ATTR_COLD() +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 5, 6) +void rb_assert_failure_detail(const char *file, int line, const char *name, const char *expr, const char *fmt, ...); RBIMPL_SYMBOL_EXPORT_END() #ifdef RUBY_FUNCTION_NAME_STRING @@ -147,8 +153,22 @@ RBIMPL_SYMBOL_EXPORT_END() * * @param mesg The message to display. */ -#define RUBY_ASSERT_FAIL(mesg) \ +#if defined(HAVE___VA_OPT__) +# if RBIMPL_HAS_WARNING("-Wgnu-zero-variadic-macro-arguments") +/* __VA_OPT__ is to be used for the zero variadic macro arguments + * cases. */ +RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) +# endif +# define RUBY_ASSERT_FAIL(mesg, ...) \ + rb_assert_failure##__VA_OPT__(_detail)( \ + __FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_FAIL(mesg, ...) \ rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) +#else +# define RUBY_ASSERT_FAIL(mesg) \ + rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) +#endif /** * Asserts that the expression is truthy. If not aborts with the message. @@ -156,15 +176,27 @@ RBIMPL_SYMBOL_EXPORT_END() * @param expr What supposedly evaluates to true. * @param mesg The message to display on failure. */ -#define RUBY_ASSERT_MESG(expr, mesg) \ +#if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG(expr, ...) \ + (RB_LIKELY(expr) ? RBIMPL_ASSERT_NOTHING : RUBY_ASSERT_FAIL(__VA_ARGS__)) +#else +# define RUBY_ASSERT_MESG(expr, mesg) \ (RB_LIKELY(expr) ? RBIMPL_ASSERT_NOTHING : RUBY_ASSERT_FAIL(mesg)) +#endif /** * A variant of #RUBY_ASSERT that does not interface with #RUBY_DEBUG. * * @copydetails #RUBY_ASSERT */ -#define RUBY_ASSERT_ALWAYS(expr) RUBY_ASSERT_MESG((expr), #expr) +#if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_ALWAYS(expr, ...) \ + RUBY_ASSERT_MESG(expr, #expr __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_ALWAYS(expr, ...) RUBY_ASSERT_MESG(expr, #expr) +#else +# define RUBY_ASSERT_ALWAYS(expr) RUBY_ASSERT_MESG((expr), #expr) +#endif /** * Asserts that the given expression is truthy if and only if #RUBY_DEBUG is truthy. @@ -172,9 +204,20 @@ RBIMPL_SYMBOL_EXPORT_END() * @param expr What supposedly evaluates to true. */ #if RUBY_DEBUG -# define RUBY_ASSERT(expr) RUBY_ASSERT_MESG((expr), #expr) +# if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT(expr, ...) \ + RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) +# elif !defined(__cplusplus) +# define RUBY_ASSERT(expr, ...) RUBY_ASSERT_MESG((expr), #expr) +# else +# define RUBY_ASSERT(expr) RUBY_ASSERT_MESG((expr), #expr) +# endif #else -# define RUBY_ASSERT(expr) RBIMPL_ASSERT_NOTHING +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT(/* expr, */...) RBIMPL_ASSERT_NOTHING +# else +# define RUBY_ASSERT(expr) RBIMPL_ASSERT_NOTHING +# endif #endif /** @@ -187,9 +230,20 @@ RBIMPL_SYMBOL_EXPORT_END() /* Currently `RUBY_DEBUG == ! defined(NDEBUG)` is always true. There is no * difference any longer between this one and `RUBY_ASSERT`. */ #if defined(NDEBUG) -# define RUBY_ASSERT_NDEBUG(expr) RBIMPL_ASSERT_NOTHING +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_NDEBUG(/* expr, */...) RBIMPL_ASSERT_NOTHING +# else +# define RUBY_ASSERT_NDEBUG(expr) RBIMPL_ASSERT_NOTHING +# endif #else -# define RUBY_ASSERT_NDEBUG(expr) RUBY_ASSERT_MESG((expr), #expr) +# if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_NDEBUG(expr, ...) \ + RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) +# elif !defined(__cplusplus) +# define RUBY_ASSERT_NDEBUG(expr, ...) RUBY_ASSERT_MESG((expr), #expr) +# else +# define RUBY_ASSERT_NDEBUG(expr) RUBY_ASSERT_MESG((expr), #expr) +# endif #endif /** @@ -197,10 +251,20 @@ RBIMPL_SYMBOL_EXPORT_END() * @param mesg The message to display on failure. */ #if RUBY_DEBUG -# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) RUBY_ASSERT_MESG((expr), (mesg)) +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG_WHEN(cond, /* expr, */...) \ + RUBY_ASSERT_MESG(__VA_ARGS__) +# else +# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) RUBY_ASSERT_MESG((expr), (mesg)) +# endif #else -# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) \ +# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# define RUBY_ASSERT_MESG_WHEN(cond, expr, ...) \ + ((cond) ? RUBY_ASSERT_MESG((expr), __VA_ARGS__) : RBIMPL_ASSERT_NOTHING) +# else +# define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) \ ((cond) ? RUBY_ASSERT_MESG((expr), (mesg)) : RBIMPL_ASSERT_NOTHING) +# endif #endif /** @@ -210,7 +274,14 @@ RBIMPL_SYMBOL_EXPORT_END() * @param cond Extra condition that shall hold for assertion to take effect. * @param expr What supposedly evaluates to true. */ -#define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) +#if defined(HAVE___VA_OPT__) +# define RUBY_ASSERT_WHEN(cond, expr, ...) \ + RUBY_ASSERT_MESG_WHEN(cond, expr, #expr __VA_OPT__(,) __VA_ARGS__) +#elif !defined(__cplusplus) +# define RUBY_ASSERT_WHEN(cond, expr, ...) RUBY_ASSERT_MESG_WHEN(cond, expr, #expr) +#else +# define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) +#endif /** * This is either #RUBY_ASSERT or #RBIMPL_ASSUME, depending on #RUBY_DEBUG. From f3cc1f9a703ab56f5601d6c7b6a964333b668c17 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 31 Jan 2024 15:11:59 +0900 Subject: [PATCH 062/142] Show actual imemo type when unexpected type --- vm_core.h | 4 ++-- vm_method.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vm_core.h b/vm_core.h index c1508736bde048..448e17ba526b8f 100644 --- a/vm_core.h +++ b/vm_core.h @@ -56,12 +56,12 @@ #define RVALUE_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])) #if VM_CHECK_MODE > 0 -#define VM_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(VM_CHECK_MODE > 0, expr, #expr) +#define VM_ASSERT(/*expr, */...) RUBY_ASSERT_WHEN(VM_CHECK_MODE > 0, __VA_ARGS__) #define VM_UNREACHABLE(func) rb_bug(#func ": unreachable") #define RUBY_ASSERT_CRITICAL_SECTION #define RUBY_DEBUG_THREAD_SCHEDULE() rb_thread_schedule() #else -#define VM_ASSERT(expr) ((void)0) +#define VM_ASSERT(/*expr, */...) ((void)0) #define VM_UNREACHABLE(func) UNREACHABLE #define RUBY_DEBUG_THREAD_SCHEDULE() #endif diff --git a/vm_method.c b/vm_method.c index 1f14d82bcd4c12..a4d0a9f4112e9d 100644 --- a/vm_method.c +++ b/vm_method.c @@ -118,7 +118,7 @@ rb_vm_mtbl_dump(const char *msg, VALUE klass, ID target_mid) static inline void vm_cme_invalidate(rb_callable_method_entry_t *cme) { - VM_ASSERT(IMEMO_TYPE_P(cme, imemo_ment)); + VM_ASSERT(IMEMO_TYPE_P(cme, imemo_ment), "cme: %d", imemo_type((VALUE)cme)); VM_ASSERT(callable_method_entry_p(cme)); METHOD_ENTRY_INVALIDATED_SET(cme); RB_DEBUG_COUNTER_INC(cc_cme_invalidate); From 34581410f20e2d2252b9da19a803bf5cc1ae23e1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Feb 2024 11:41:26 +0900 Subject: [PATCH 063/142] Extract `RBIMPL_VA_OPT_ARGS` Similar to splat argument in Ruby, which be expanded to `__VA_ARGS__` with a leading comma if any arguments given, otherwise empty. --- include/ruby/assert.h | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/include/ruby/assert.h b/include/ruby/assert.h index ebeae3e7be547f..ceab090427924a 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -159,13 +159,19 @@ RBIMPL_SYMBOL_EXPORT_END() * cases. */ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) # endif +# define RBIMPL_VA_OPT_ARGS(...) __VA_OPT__(,) __VA_ARGS__ + # define RUBY_ASSERT_FAIL(mesg, ...) \ rb_assert_failure##__VA_OPT__(_detail)( \ - __FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg __VA_OPT__(,) __VA_ARGS__) + __FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) #elif !defined(__cplusplus) +# define RBIMPL_VA_OPT_ARGS(...) + # define RUBY_ASSERT_FAIL(mesg, ...) \ rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) #else +# undef RBIMPL_VA_OPT_ARGS + # define RUBY_ASSERT_FAIL(mesg) \ rb_assert_failure(__FILE__, __LINE__, RBIMPL_ASSERT_FUNC, mesg) #endif @@ -176,7 +182,7 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) * @param expr What supposedly evaluates to true. * @param mesg The message to display on failure. */ -#if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +#if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_MESG(expr, ...) \ (RB_LIKELY(expr) ? RBIMPL_ASSERT_NOTHING : RUBY_ASSERT_FAIL(__VA_ARGS__)) #else @@ -189,11 +195,9 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) * * @copydetails #RUBY_ASSERT */ -#if defined(HAVE___VA_OPT__) +#if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_ALWAYS(expr, ...) \ - RUBY_ASSERT_MESG(expr, #expr __VA_OPT__(,) __VA_ARGS__) -#elif !defined(__cplusplus) -# define RUBY_ASSERT_ALWAYS(expr, ...) RUBY_ASSERT_MESG(expr, #expr) + RUBY_ASSERT_MESG(expr, #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) #else # define RUBY_ASSERT_ALWAYS(expr) RUBY_ASSERT_MESG((expr), #expr) #endif @@ -204,16 +208,14 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) * @param expr What supposedly evaluates to true. */ #if RUBY_DEBUG -# if defined(HAVE___VA_OPT__) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT(expr, ...) \ - RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) -# elif !defined(__cplusplus) -# define RUBY_ASSERT(expr, ...) RUBY_ASSERT_MESG((expr), #expr) + RUBY_ASSERT_MESG((expr), #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) # else # define RUBY_ASSERT(expr) RUBY_ASSERT_MESG((expr), #expr) # endif #else -# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT(/* expr, */...) RBIMPL_ASSERT_NOTHING # else # define RUBY_ASSERT(expr) RBIMPL_ASSERT_NOTHING @@ -230,17 +232,15 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) /* Currently `RUBY_DEBUG == ! defined(NDEBUG)` is always true. There is no * difference any longer between this one and `RUBY_ASSERT`. */ #if defined(NDEBUG) -# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_NDEBUG(/* expr, */...) RBIMPL_ASSERT_NOTHING # else # define RUBY_ASSERT_NDEBUG(expr) RBIMPL_ASSERT_NOTHING # endif #else -# if defined(HAVE___VA_OPT__) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_NDEBUG(expr, ...) \ - RUBY_ASSERT_MESG((expr), #expr __VA_OPT__(,) __VA_ARGS__) -# elif !defined(__cplusplus) -# define RUBY_ASSERT_NDEBUG(expr, ...) RUBY_ASSERT_MESG((expr), #expr) + RUBY_ASSERT_MESG((expr), #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) # else # define RUBY_ASSERT_NDEBUG(expr) RUBY_ASSERT_MESG((expr), #expr) # endif @@ -251,14 +251,14 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) * @param mesg The message to display on failure. */ #if RUBY_DEBUG -# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_MESG_WHEN(cond, /* expr, */...) \ RUBY_ASSERT_MESG(__VA_ARGS__) # else # define RUBY_ASSERT_MESG_WHEN(cond, expr, mesg) RUBY_ASSERT_MESG((expr), (mesg)) # endif #else -# if defined(HAVE___VA_OPT__) || !defined(__cplusplus) +# if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_MESG_WHEN(cond, expr, ...) \ ((cond) ? RUBY_ASSERT_MESG((expr), __VA_ARGS__) : RBIMPL_ASSERT_NOTHING) # else @@ -274,11 +274,9 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) * @param cond Extra condition that shall hold for assertion to take effect. * @param expr What supposedly evaluates to true. */ -#if defined(HAVE___VA_OPT__) +#if defined(RBIMPL_VA_OPT_ARGS) # define RUBY_ASSERT_WHEN(cond, expr, ...) \ - RUBY_ASSERT_MESG_WHEN(cond, expr, #expr __VA_OPT__(,) __VA_ARGS__) -#elif !defined(__cplusplus) -# define RUBY_ASSERT_WHEN(cond, expr, ...) RUBY_ASSERT_MESG_WHEN(cond, expr, #expr) + RUBY_ASSERT_MESG_WHEN(cond, expr, #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) #else # define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) #endif From 443c5c06ac2be84059a7c4435c37deb8de2428d6 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 8 Feb 2024 23:17:01 +0900 Subject: [PATCH 064/142] Bundle rbs-3.4.4 (#9883) --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index b914fbc084ffdf..ba300bef788e28 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -17,7 +17,7 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.4.0.1 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime -rbs 3.4.3 https://github.com/ruby/rbs +rbs 3.4.4 https://github.com/ruby/rbs typeprof 0.21.10 https://github.com/ruby/typeprof debug 1.9.1 https://github.com/ruby/debug 91fe870eeceb9ffbbc7f1bb4673f9e2f6a2c1f60 racc 1.7.3 https://github.com/ruby/racc From 6756dbf3bbdd71967472ade018c84ddedefd8a6c Mon Sep 17 00:00:00 2001 From: git Date: Thu, 8 Feb 2024 14:17:39 +0000 Subject: [PATCH 065/142] Update bundled gems list at 443c5c06ac2be84059a7c4435c37de [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2ef33a42c3767e..f7a5a60728c7b0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -40,7 +40,7 @@ The following bundled gems are updated. * net-ftp 0.3.4 * net-imap 0.4.10 * net-smtp 0.4.0.1 -* rbs 3.4.3 +* rbs 3.4.4 * typeprof 0.21.10 * debug 1.9.1 From 01fd262e62076277a41af72ea13f20deb1b462a2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 7 Feb 2024 11:30:20 -0500 Subject: [PATCH 066/142] Fix crash when checking symbol encoding [Bug #20245] We sometimes pass in a fake string to sym_check_asciionly. This can crash if sym_check_asciionly raises because it creates a CFP with the fake string as the receiver which will crash if GC tries to mark the CFP. For example, the following script crashes: GC.stress = true Object.const_defined?("\xC3") --- symbol.c | 17 ++++++++++------- test/ruby/test_module.rb | 8 ++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/symbol.c b/symbol.c index 2c2ab4380c5598..bdbfbae831295a 100644 --- a/symbol.c +++ b/symbol.c @@ -581,11 +581,14 @@ register_static_symid_str(ID id, VALUE str) } static int -sym_check_asciionly(VALUE str) +sym_check_asciionly(VALUE str, bool fake_str) { if (!rb_enc_asciicompat(rb_enc_get(str))) return FALSE; switch (rb_enc_str_coderange(str)) { case ENC_CODERANGE_BROKEN: + if (fake_str) { + str = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), rb_enc_get(str)); + } rb_raise(rb_eEncodingError, "invalid symbol in encoding %s :%+"PRIsVALUE, rb_enc_name(rb_enc_get(str)), str); case ENC_CODERANGE_7BIT: @@ -778,7 +781,7 @@ intern_str(VALUE str, int mutable) id = rb_str_symname_type(str, IDSET_ATTRSET_FOR_INTERN); if (id == (ID)-1) id = ID_JUNK; - if (sym_check_asciionly(str)) { + if (sym_check_asciionly(str, false)) { if (!mutable) str = rb_str_dup(str); rb_enc_associate(str, rb_usascii_encoding()); } @@ -869,7 +872,7 @@ rb_str_intern(VALUE str) else if (USE_SYMBOL_GC) { rb_encoding *enc = rb_enc_get(str); rb_encoding *ascii = rb_usascii_encoding(); - if (enc != ascii && sym_check_asciionly(str)) { + if (enc != ascii && sym_check_asciionly(str, false)) { str = rb_str_dup(str); rb_enc_associate(str, ascii); OBJ_FREEZE(str); @@ -1116,7 +1119,7 @@ rb_check_id(volatile VALUE *namep) *namep = name; } - sym_check_asciionly(name); + sym_check_asciionly(name, false); return lookup_str_id(name); } @@ -1175,7 +1178,7 @@ rb_check_symbol(volatile VALUE *namep) *namep = name; } - sym_check_asciionly(name); + sym_check_asciionly(name, false); if ((sym = lookup_str_sym(name)) != 0) { return sym; @@ -1190,7 +1193,7 @@ rb_check_id_cstr(const char *ptr, long len, rb_encoding *enc) struct RString fake_str; const VALUE name = rb_setup_fake_str(&fake_str, ptr, len, enc); - sym_check_asciionly(name); + sym_check_asciionly(name, true); return lookup_str_id(name); } @@ -1202,7 +1205,7 @@ rb_check_symbol_cstr(const char *ptr, long len, rb_encoding *enc) struct RString fake_str; const VALUE name = rb_setup_fake_str(&fake_str, ptr, len, enc); - sym_check_asciionly(name); + sym_check_asciionly(name, true); if ((sym = lookup_str_sym(name)) != 0) { return sym; diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index ca157460020caa..4722fa22e0b745 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -253,6 +253,14 @@ def test_const_defined? assert_operator(Math, :const_defined?, "PI") assert_not_operator(Math, :const_defined?, :IP) assert_not_operator(Math, :const_defined?, "IP") + + # Test invalid symbol name + # [Bug #20245] + EnvUtil.under_gc_stress do + assert_raise(EncodingError) do + Math.const_defined?("\xC3") + end + end end def each_bad_constants(m, &b) From b74c8abd1132824f95d81309f96645f272c064dc Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 07:22:07 -0800 Subject: [PATCH 067/142] YJIT: Skip pushing a frame for Hash#empty? (#9875) --- hash.c | 2 +- yjit/src/codegen.rs | 23 +++++++++++++++++++++++ yjit/src/cruby.rs | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/hash.c b/hash.c index 91f1e73a393b8d..201ada4f6ed687 100644 --- a/hash.c +++ b/hash.c @@ -3015,7 +3015,7 @@ rb_hash_size_num(VALUE hash) * {foo: 0, bar: 1, baz: 2}.empty? # => false */ -static VALUE +VALUE rb_hash_empty_p(VALUE hash) { return RBOOL(RHASH_EMPTY_P(hash)); diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 4eaeebd503a660..b7df2b44f8997e 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -5499,6 +5499,27 @@ fn jit_rb_ary_push( true } +// Just a leaf method, but not using `Primitive.attr! :leaf` since BOP methods can't use it. +fn jit_rb_hash_empty_p( + _jit: &mut JITState, + asm: &mut Assembler, + _ocb: &mut OutlinedCb, + _ci: *const rb_callinfo, + _cme: *const rb_callable_method_entry_t, + _block: Option, + _argc: i32, + _known_recv_class: *const VALUE, +) -> bool { + asm_comment!(asm, "Hash#empty?"); + + let hash_opnd = asm.stack_pop(1); + let ret = asm.ccall(rb_hash_empty_p as *const u8, vec![hash_opnd]); + + let ret_opnd = asm.stack_push(Type::UnknownImm); + asm.mov(ret_opnd, ret); + true +} + fn jit_obj_respond_to( jit: &mut JITState, asm: &mut Assembler, @@ -9360,6 +9381,8 @@ pub fn yjit_reg_method_codegen_fns() { yjit_reg_method(rb_cArray, "size", jit_rb_ary_length); yjit_reg_method(rb_cArray, "<<", jit_rb_ary_push); + yjit_reg_method(rb_cHash, "empty?", jit_rb_hash_empty_p); + yjit_reg_method(rb_mKernel, "respond_to?", jit_obj_respond_to); yjit_reg_method(rb_mKernel, "block_given?", jit_rb_f_block_given_p); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 0f3519b820dcca..78cb2d65ea91c3 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -107,11 +107,12 @@ pub use autogened::*; // TODO: For #defines that affect memory layout, we need to check for them // on build and fail if they're wrong. e.g. USE_FLONUM *must* be true. -// These are functions we expose from vm_insnhelper.c, not in any header. +// These are functions we expose from C files, not in any header. // Parsing it would result in a lot of duplicate definitions. // Use bindgen for functions that are defined in headers or in yjit.c. #[cfg_attr(test, allow(unused))] // We don't link against C code when testing extern "C" { + pub fn rb_hash_empty_p(hash: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_concat_to_array(ary1: VALUE, ary2st: VALUE) -> VALUE; From c6b391214c13aa89bddad8aa2ba334fab98ab03c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 7 Feb 2024 15:52:34 -0500 Subject: [PATCH 068/142] [DOC] Improve flags of string --- string.c | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/string.c b/string.c index 9ed5de5a323ecb..920ab14363ea9d 100644 --- a/string.c +++ b/string.c @@ -78,24 +78,39 @@ VALUE rb_cString; VALUE rb_cSymbol; -/* FLAGS of RString +/* Flags of RString * * 1: RSTRING_NOEMBED - * 2: STR_SHARED (== ELTS_SHARED) - * 5: STR_SHARED_ROOT (RSTRING_NOEMBED==1 && STR_SHARED == 0, there may be - * other strings that rely on this string's buffer) - * 6: STR_BORROWED (when RSTRING_NOEMBED==1 && klass==0, unsafe to recycle - * early, specific to rb_str_tmp_frozen_{acquire,release}) - * 7: STR_TMPLOCK (set when a pointer to the buffer is passed to syscall - * such as read(2). Any modification and realloc is prohibited) - * - * 8-9: ENC_CODERANGE (2 bits) - * 10-16: ENCODING (7 bits == 128) + * The string is not embedded. When a string is embedded, the contents + * follow the header. When a string is not embedded, the contents is + * on a separately allocated buffer. + * 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). + * 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. + * 6: STR_BORROWED + * When RSTRING_NOEMBED is set and klass is 0, this string is unsafe + * to be unshared by rb_str_tmp_frozen_release. + * 7: STR_TMPLOCK + * The pointer to the buffer is passed to a system call such as + * read(2). Any modification and realloc is prohibited. + * 8-9: ENC_CODERANGE + * Stores the coderange of the string. + * 10-16: ENCODING + * Stores the encoding of the string. * 17: RSTRING_FSTR - * 18: STR_NOFREE (do not free this string's buffer when a String is freed. - * used for a string object based on C string literal) - * 19: STR_FAKESTR (when RVALUE is not managed by GC. Typically, the string - * object header is temporarily allocated on C stack) + * The string is a fstring. The string is deduplicated in the fstring + * table. + * 18: STR_NOFREE + * Do not free this string's buffer when the string is reclaimed + * by the garbage collector. Used for when the string buffer is a C + * string literal. + * 19: STR_FAKESTR + * The string is not allocated or managed by the garbage collector. + * Typically, the string object header (struct RString) is temporarily + * allocated on C stack. */ #define RUBY_MAX_CHAR_LEN 16 From 366af4679e5de6720a952768a3f17a05ff7df4cc Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 7 Feb 2024 16:17:15 +0000 Subject: [PATCH 069/142] [ruby/prism] RipperCompat: support for more features. * add bin/prism ripper to compare Ripper output * block arg handling is quirky, do it per-call-site * block required params * boolean values * various assign-operator support * breaks, early fragile begin/rescue/end * more fixtures being checked https://github.com/ruby/prism/commit/31732cb720 --- lib/prism/ripper_compat.rb | 154 +++++++++++++++++++++++++------ test/prism/ripper_compat_test.rb | 30 +++++- 2 files changed, 156 insertions(+), 28 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 44983f8596ccb3..5c3b56d01b1386 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -132,7 +132,12 @@ def visit_call_node(node) end # A non-operator method call with parentheses - args = on_arg_paren(node.arguments.nil? ? nil : args_node_to_arguments(node.arguments)) + + args = if node.arguments.nil? + on_arg_paren(nil) + else + on_arg_paren(on_args_add_block(visit_elements(node.arguments.arguments), false)) + end bounds(node.message_loc) ident_val = on_ident(node.message) @@ -142,31 +147,92 @@ def visit_call_node(node) if node.block block_val = visit(node.block) - return on_method_add_block(args_call_val, on_brace_block(nil, block_val)) + return on_method_add_block(args_call_val, block_val) else return args_call_val end end - # Visit a BlockNode + # Visit a LocalVariableAndWriteNode. + def visit_local_variable_and_write_node(node) + visit_binary_op_assign(node) + end + + # Visit a LocalVariableOrWriteNode. + def visit_local_variable_or_write_node(node) + visit_binary_op_assign(node) + end + + # Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes. + def visit_local_variable_operator_write_node(node) + visit_binary_op_assign(node, operator: node.operator.to_s + "=") + end + + # Visit a LocalVariableReadNode. + def visit_local_variable_read_node(node) + bounds(node.location) + ident_val = on_ident(node.slice) + + on_var_ref(ident_val) + end + + # Visit a BlockNode. def visit_block_node(node) - if node.body.nil? - on_stmts_add(on_stmts_new, on_void_stmt) - else - visit(node.body) - end + params_val = node.parameters.nil? ? nil : visit(node.parameters) + + body_val = node.body.nil? ? on_stmts_add(on_stmts_new, on_void_stmt) : visit(node.body) + + on_brace_block(params_val, body_val) + end + + # Visit a BlockParametersNode. + def visit_block_parameters_node(node) + on_block_var(visit(node.parameters), no_block_value) + end + + # Visit a ParametersNode. + # This will require expanding as we support more kinds of parameters. + def visit_parameters_node(node) + #on_params(required, optional, nil, nil, nil, nil, nil) + on_params(node.requireds.map { |n| visit(n) }, nil, nil, nil, nil, nil, nil) end - # Visit an AndNode + # Visit a RequiredParameterNode. + def visit_required_parameter_node(node) + bounds(node.location) + on_ident(node.name.to_s) + end + + # Visit a BreakNode. + def visit_break_node(node) + return on_break(on_args_new) if node.arguments.nil? + + args_val = visit_elements(node.arguments.arguments) + on_break(on_args_add_block(args_val,false)) + end + + # Visit an AndNode. def visit_and_node(node) visit_binary_operator(node) end - # Visit an OrNode + # Visit an OrNode. def visit_or_node(node) visit_binary_operator(node) end + # Visit a TrueNode. + def visit_true_node(node) + bounds(node.location) + on_var_ref(on_kw(node.slice)) + end + + # Visit a FalseNode. + def visit_false_node(node) + bounds(node.location) + on_var_ref(on_kw(node.slice)) + end + # Visit a FloatNode node. def visit_float_node(node) visit_number(node) { |text| on_float(text) } @@ -195,6 +261,19 @@ def visit_parentheses_node(node) on_paren(body) end + # Visit a BeginNode node. + # This is not at all bulletproof against different structures of begin/rescue/else/ensure/end. + def visit_begin_node(node) + rescue_val = node.rescue_clause ? on_rescue(nil, nil, visit(node.rescue_clause), nil) : nil + ensure_val = node.ensure_clause ? on_ensure(visit(node.ensure_clause.statements)) : nil + on_begin(on_bodystmt(visit(node.statements), rescue_val, nil, ensure_val)) + end + + # Visit a RescueNode node. + def visit_rescue_node(node) + visit(node.statements) + end + # Visit a ProgramNode node. def visit_program_node(node) statements = visit(node.statements) @@ -260,7 +339,7 @@ def visit_no_paren_call(node) raise NotImplementedError, "More than two arguments for operator" end elsif node.call_operator_loc.nil? - # In Ripper a method call like "puts myvar" with no parenthesis is a "command". + # In Ripper a method call like "puts myvar" with no parentheses is a "command". bounds(node.message_loc) ident_val = on_ident(node.message) @@ -268,11 +347,20 @@ def visit_no_paren_call(node) if node.block block_val = visit(node.block) # In these calls, even if node.arguments is nil, we still get an :args_new call. - method_args_val = on_method_add_arg(on_fcall(ident_val), args_node_to_arguments(node.arguments)) - return on_method_add_block(method_args_val, on_brace_block(nil, block_val)) + args = if node.arguments.nil? + on_args_new + else + on_args_add_block(visit_elements(node.arguments.arguments)) + end + method_args_val = on_method_add_arg(on_fcall(ident_val), args) + return on_method_add_block(method_args_val, block_val) else - args = node.arguments.nil? ? nil : args_node_to_arguments(node.arguments) - return on_command(ident_val, args) + if node.arguments.nil? + return on_command(ident_val, nil) + else + args = on_args_add_block(visit_elements(node.arguments.arguments), false) + return on_command(ident_val, args) + end end else operator = node.call_operator_loc.slice @@ -289,7 +377,7 @@ def visit_no_paren_call(node) if node.block block_val = visit(node.block) - return on_method_add_block(call_val, on_brace_block(nil, block_val)) + return on_method_add_block(call_val, block_val) else return call_val end @@ -299,17 +387,6 @@ def visit_no_paren_call(node) end end - # Ripper generates an interesting format of argument list. - # It seems to be very location-specific. We should get rid of - # this method and make it clearer how it's done in each place. - def args_node_to_arguments(args_node) - return on_args_new if args_node.nil? - - args = visit_elements(args_node.arguments) - - on_args_add_block(args, false) - end - # Visit a list of elements, like the elements of an array or arguments. def visit_elements(elements) bounds(elements.first.location) @@ -318,6 +395,17 @@ def visit_elements(elements) end end + # Visit an operation-and-assign node, such as +=. + def visit_binary_op_assign(node, operator: node.operator) + bounds(node.name_loc) + ident_val = on_ident(node.name.to_s) + + bounds(node.operator_loc) + op_val = on_op(operator) + + on_opassign(on_var_field(ident_val), op_val, visit(node.value)) + end + # Visit a node that represents a number. We need to explicitly handle the # unary - operator. def visit_number(node) @@ -348,6 +436,18 @@ def visit_unary_operator(value) end end + if RUBY_ENGINE == "jruby" + # For JRuby, "no block" in an on_block_var is nil + def no_block_value + nil + end + else + # For CRuby et al, "no block" in an on_block_var is false + def no_block_value + false + end + end + # Visit a binary operator node like an AndNode or OrNode def visit_binary_operator(node) left_val = visit(node.left) diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index 1aaade046f2df0..c8fc208dff0ccf 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -27,6 +27,7 @@ def test_binary_parens def test_method_calls_with_variable_names assert_equivalent("foo") assert_equivalent("foo()") + assert_equivalent("foo -7") assert_equivalent("foo(-7)") assert_equivalent("foo(1, 2, 3)") assert_equivalent("foo 1") @@ -49,9 +50,16 @@ def test_method_calls_with_variable_names assert_equivalent("foo(1) { bar }") assert_equivalent("foo(bar)") assert_equivalent("foo(bar(1))") + assert_equivalent("foo(bar(1)) { 7 }") assert_equivalent("foo bar(1)") - # assert_equivalent("foo(bar 1)") # This succeeds for me locally but fails on CI + end + + def test_method_call_blocks + assert_equivalent("foo { |a| a }") + + # assert_equivalent("foo(bar 1)") # assert_equivalent("foo bar 1") + # assert_equivalent("foo(bar 1) { 7 }") end def test_method_calls_on_immediate_values @@ -85,6 +93,23 @@ def test_numbers assert_equivalent("[1ri, -1ri, +1ri, 1.5ri, -1.5ri, +1.5ri]") end + def test_begin_rescue + assert_equivalent("begin a; rescue; c; ensure b; end") + end + + def test_break + assert_equivalent("foo { break }") + assert_equivalent("foo { break 7 }") + assert_equivalent("foo { break [1, 2, 3] }") + end + + def test_op_assign + assert_equivalent("a += b") + assert_equivalent("a -= b") + assert_equivalent("a *= b") + assert_equivalent("a /= b") + end + private def assert_equivalent(source) @@ -100,6 +125,9 @@ class RipperCompatFixturesTest < TestCase #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base] relatives = [ "arithmetic.txt", + "booleans.txt", + "boolean_operators.txt", + # "break.txt", # No longer parseable by Ripper in CRuby 3.3.0+ "comments.txt", "integer_operations.txt", ] From 6aceb91de0c13dd3b2b98ecfede806aa3cfe883c Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:20:05 +0000 Subject: [PATCH 070/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/bce0a5c916 Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 5c3b56d01b1386..d6d7427388aad7 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -165,7 +165,7 @@ def visit_local_variable_or_write_node(node) # Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes. def visit_local_variable_operator_write_node(node) - visit_binary_op_assign(node, operator: node.operator.to_s + "=") + visit_binary_op_assign(node, operator: "#{node.operator}=") end # Visit a LocalVariableReadNode. From 70bc4ce34f54b6d865dba638ffe512522625ed9e Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:20:33 +0000 Subject: [PATCH 071/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/6e2bf9c8cd Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index d6d7427388aad7..aebd04660e8cee 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -200,7 +200,7 @@ def visit_parameters_node(node) # Visit a RequiredParameterNode. def visit_required_parameter_node(node) bounds(node.location) - on_ident(node.name.to_s) + on_ident(node.name.name) end # Visit a BreakNode. From c1aba5d97b4f2963af80118ddaf6941f1f909092 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:20:41 +0000 Subject: [PATCH 072/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/8271ce5ec9 Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index aebd04660e8cee..dc32470c96acef 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -208,7 +208,7 @@ def visit_break_node(node) return on_break(on_args_new) if node.arguments.nil? args_val = visit_elements(node.arguments.arguments) - on_break(on_args_add_block(args_val,false)) + on_break(on_args_add_block(args_val, false)) end # Visit an AndNode. From 3f0aa554493f154280be73045522efe562ac4de7 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:21:01 +0000 Subject: [PATCH 073/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/5eac08f699 Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index dc32470c96acef..79b9ec1f744b4a 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -224,7 +224,7 @@ def visit_or_node(node) # Visit a TrueNode. def visit_true_node(node) bounds(node.location) - on_var_ref(on_kw(node.slice)) + on_var_ref(on_kw("true")) end # Visit a FalseNode. From f8b8a6780c9922a4792ca4a1066dddfac08638a6 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:21:08 +0000 Subject: [PATCH 074/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/03addf2d3d Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 79b9ec1f744b4a..05334b40258125 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -230,7 +230,7 @@ def visit_true_node(node) # Visit a FalseNode. def visit_false_node(node) bounds(node.location) - on_var_ref(on_kw(node.slice)) + on_var_ref(on_kw("false")) end # Visit a FloatNode node. From 0c73553052748a9ac1ef196f6e6db9608aca0d9b Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:23:27 +0000 Subject: [PATCH 075/142] [ruby/prism] Add Kevin's visit_all change and change the parent class to Compiler. https://github.com/ruby/prism/commit/bbdba3f42d Co-Authored-By: Kevin Newton --- lib/prism/ripper_compat.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 05334b40258125..d5d68d2e661784 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -17,7 +17,7 @@ module Prism # # To use this class, you treat `Prism::RipperCompat` effectively as you would # treat the `Ripper` class. - class RipperCompat < Visitor + class RipperCompat < Compiler # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that # returns the arrays of [type, *children]. class SexpBuilder < RipperCompat @@ -194,7 +194,7 @@ def visit_block_parameters_node(node) # This will require expanding as we support more kinds of parameters. def visit_parameters_node(node) #on_params(required, optional, nil, nil, nil, nil, nil) - on_params(node.requireds.map { |n| visit(n) }, nil, nil, nil, nil, nil, nil) + on_params(visit_all(node.requireds), nil, nil, nil, nil, nil, nil) end # Visit a RequiredParameterNode. From 1983949811607fb87584943749a0cf9056a49c01 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 15:33:16 +0000 Subject: [PATCH 076/142] [ruby/prism] Looks like Symbol#name wasn't a thing in 2.7, so need to switch back to to_s https://github.com/ruby/prism/commit/0b90c9a398 --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index d5d68d2e661784..6298fa8c0f1c60 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -200,7 +200,7 @@ def visit_parameters_node(node) # Visit a RequiredParameterNode. def visit_required_parameter_node(node) bounds(node.location) - on_ident(node.name.name) + on_ident(node.name.to_s) end # Visit a BreakNode. From 50b4ef29b2f326cf62f07ae3f136fc2baec18227 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 08:03:07 -0800 Subject: [PATCH 077/142] YJIT: Use jit_prepare_call_with_gc as much as possible (#9874) * YJIT: Use jit_prepare_call_with_gc as much as possible * Stop assuming vm_defined doesn't make a call --- yjit/src/codegen.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index b7df2b44f8997e..e5180ced164054 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2326,7 +2326,7 @@ fn gen_set_ivar( handle_opt_send_shift_stack(asm, argc); } - // Save the PC and SP because the callee may allocate + // Save the PC and SP because the callee may allocate or raise FrozenError // Note that this modifies REG_SP, which is why we do it first jit_prepare_non_leaf_call(jit, asm); @@ -2401,7 +2401,7 @@ fn gen_get_ivar( // VALUE rb_ivar_get(VALUE obj, ID id) asm_comment!(asm, "call rb_ivar_get()"); - // The function could raise exceptions. + // The function could raise RactorIsolationError. jit_prepare_non_leaf_call(jit, asm); let ivar_val = asm.ccall(rb_ivar_get as *const u8, vec![recv, Opnd::UImm(ivar_name)]); @@ -2658,7 +2658,7 @@ fn gen_setinstancevariable( let ic = jit.get_arg(1).as_u64(); // type IVC - // The function could raise exceptions. + // The function could raise FrozenError. // Note that this modifies REG_SP, which is why we do it first jit_prepare_non_leaf_call(jit, asm); @@ -2716,7 +2716,7 @@ fn gen_setinstancevariable( // It allocates so can trigger GC, which takes the VM lock // so could yield to a different ractor. - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); asm.ccall(rb_ensure_iv_list_size as *const u8, vec![ recv, @@ -2797,7 +2797,7 @@ fn gen_defined( gen_block_given(jit, asm, out_opnd, pushval.into(), Qnil.into()); } _ => { - // Save the PC and SP because the callee may allocate + // Save the PC and SP because the callee may allocate or call #respond_to? // Note that this modifies REG_SP, which is why we do it first jit_prepare_non_leaf_call(jit, asm); @@ -2855,7 +2855,7 @@ fn gen_definedivar( // Save the PC and SP because the callee may allocate // Note that this modifies REG_SP, which is why we do it first - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); // Call rb_ivar_defined(recv, ivar_name) let def_result = asm.ccall(rb_ivar_defined as *const u8, vec![recv, ivar_name.into()]); @@ -3504,6 +3504,7 @@ fn gen_opt_aref_with( asm: &mut Assembler, _ocb: &mut OutlinedCb, ) -> Option{ + // We might allocate or raise jit_prepare_non_leaf_call(jit, asm); let key_opnd = Opnd::Value(jit.get_arg(0)); @@ -3830,7 +3831,7 @@ fn gen_opt_newarray_max( ) -> Option { let num = jit.get_arg(0).as_u32(); - // Save the PC and SP because we may allocate + // Save the PC and SP because we may call #max jit_prepare_non_leaf_call(jit, asm); extern "C" { @@ -3883,7 +3884,7 @@ fn gen_opt_newarray_hash( let num = jit.get_arg(0).as_u32(); - // Save the PC and SP because we may allocate + // Save the PC and SP because we may call #hash jit_prepare_non_leaf_call(jit, asm); extern "C" { @@ -3918,7 +3919,7 @@ fn gen_opt_newarray_min( let num = jit.get_arg(0).as_u32(); - // Save the PC and SP because we may allocate + // Save the PC and SP because we may call #min jit_prepare_non_leaf_call(jit, asm); extern "C" { @@ -4242,7 +4243,7 @@ fn gen_throw( } // THROW_DATA_NEW allocates. Save SP for GC and PC for allocation tracing as - // well as handling the catch table. However, not using jit_prepare_non_leaf_call + // well as handling the catch table. However, not using jit_prepare_call_with_gc // since we don't need a patch point for this implementation. jit_save_pc(jit, asm); gen_save_sp(asm); @@ -4800,7 +4801,7 @@ fn jit_rb_int_div( guard_two_fixnums(jit, asm, ocb); // rb_fix_div_fix may GC-allocate for Bignum - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); asm_comment!(asm, "Integer#/"); let obj = asm.stack_opnd(0); @@ -5185,7 +5186,7 @@ fn jit_rb_str_uplus( } // We allocate when we dup the string - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); asm.spill_temps(); // For ccall. Unconditionally spill them for RegTemps consistency. asm_comment!(asm, "Unary plus on string"); @@ -5487,7 +5488,7 @@ fn jit_rb_ary_push( asm_comment!(asm, "Array#<<"); // rb_ary_push allocates memory for buffer extension - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); let item_opnd = asm.stack_opnd(0); let ary_opnd = asm.stack_opnd(1); @@ -8596,7 +8597,7 @@ fn gen_intern( _ocb: &mut OutlinedCb, ) -> Option { // Save the PC and SP because we might allocate - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); let str = asm.stack_opnd(0); let sym = asm.ccall(rb_str_intern as *const u8, vec![str]); @@ -9046,7 +9047,7 @@ fn gen_getblockparam( let level = jit.get_arg(1).as_u32(); // Save the PC and SP because we might allocate - jit_prepare_non_leaf_call(jit, asm); + jit_prepare_call_with_gc(jit, asm); asm.spill_temps(); // For ccall. Unconditionally spill them for RegTemps consistency. // A mirror of the interpreter code. Checking for the case From 3397449846a482eaebc119738fccdd2008a72305 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 8 Feb 2024 08:03:59 -0800 Subject: [PATCH 078/142] YJIT: Report invalidation counts in non-stats mode (#9878) The `invalidation_count` and `invalidate_*` counters are all incremented using `incr_counter!` without a guard on stats mode, so they can be made always available. This could be to helpful in investigating where, how often, and what types of invalidations are occurring in a production system. --- yjit/src/stats.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 625d2291ca6570..08eb53bb5143f8 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -245,7 +245,7 @@ macro_rules! make_counters { /// The list of counters that are available without --yjit-stats. /// They are incremented only by `incr_counter!` and don't use `gen_counter_incr`. -pub const DEFAULT_COUNTERS: [Counter; 9] = [ +pub const DEFAULT_COUNTERS: [Counter; 15] = [ Counter::code_gc_count, Counter::compiled_iseq_entry, Counter::cold_iseq_entry, @@ -255,6 +255,13 @@ pub const DEFAULT_COUNTERS: [Counter; 9] = [ Counter::compiled_branch_count, Counter::compile_time_ns, Counter::max_inline_versions, + + Counter::invalidation_count, + Counter::invalidate_method_lookup, + Counter::invalidate_bop_redefined, + Counter::invalidate_ractor_spawn, + Counter::invalidate_constant_state_bump, + Counter::invalidate_constant_ic_fill, ]; /// Macro to increase a counter by name and count From b2d468fcedebdb02d342b2fdd2028c25299c4d60 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 09:36:29 -0500 Subject: [PATCH 079/142] [PRISM] Refactor call opts to only check for specific ids --- prism_compile.c | 174 +++++++++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 61 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 98ee84ea10422a..eedfa7d3aa9e94 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3874,6 +3874,61 @@ pm_compile_ensure(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *con } } +/** + * Returns true if the given call node can use the opt_str_uminus or + * opt_str_freeze instructions as an optimization with the current iseq options. + */ +static inline bool +pm_opt_str_freeze_p(const rb_iseq_t *iseq, const pm_call_node_t *node) +{ + return ( + !PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && + node->receiver != NULL && + PM_NODE_TYPE_P(node->receiver, PM_STRING_NODE) && + node->arguments == NULL && + node->block == NULL && + ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction + ); +} + +/** + * Returns true if the given call node can use the opt_aref_with optimization + * with the current iseq options. + */ +static inline bool +pm_opt_aref_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) +{ + return ( + !PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && + node->arguments != NULL && + PM_NODE_TYPE_P((const pm_node_t *) node->arguments, PM_ARGUMENTS_NODE) && + ((const pm_arguments_node_t *) node->arguments)->arguments.size == 1 && + PM_NODE_TYPE_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_NODE) && + node->block == NULL && + !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction + ); +} + +/** + * Returns true if the given call node can use the opt_aset_with optimization + * with the current iseq options. + */ +static inline bool +pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) +{ + return ( + !PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && + node->arguments != NULL && + PM_NODE_TYPE_P((const pm_node_t *) node->arguments, PM_ARGUMENTS_NODE) && + ((const pm_arguments_node_t *) node->arguments)->arguments.size == 2 && + PM_NODE_TYPE_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_NODE) && + node->block == NULL && + !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction + ); +} + /* * Compiles a prism node into instruction sequences * @@ -4180,7 +4235,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CALL_NODE: { - pm_call_node_t *call_node = (pm_call_node_t *) node; + const pm_call_node_t *call_node = (const pm_call_node_t *) node; LABEL *start = NEW_LABEL(lineno); if (call_node->block) { @@ -4189,74 +4244,71 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ID method_id = pm_constant_id_lookup(scope_node, call_node->name); - if ((method_id == idUMinus || method_id == idFreeze) && - !PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && - call_node->receiver != NULL && - PM_NODE_TYPE_P(call_node->receiver, PM_STRING_NODE) && - call_node->arguments == NULL && - call_node->block == NULL && - ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(parse_string_encoded(call_node->receiver, &((pm_string_node_t *)call_node->receiver)->unescaped, parser)); - if (method_id == idUMinus) { - ADD_INSN2(ret, &dummy_line_node, opt_str_uminus, str, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); - } - else { - ADD_INSN2(ret, &dummy_line_node, opt_str_freeze, str, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); - } - } - else if (method_id == idAREF && - !PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && - call_node->arguments && - PM_NODE_TYPE_P((pm_node_t *)call_node->arguments, PM_ARGUMENTS_NODE) && - ((pm_arguments_node_t *)call_node->arguments)->arguments.size == 1 && - PM_NODE_TYPE_P(((pm_arguments_node_t *)call_node->arguments)->arguments.nodes[0], PM_STRING_NODE) && - call_node->block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && - ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - pm_string_node_t *str_node = (pm_string_node_t *)((pm_arguments_node_t *)call_node->arguments)->arguments.nodes[0]; - VALUE str = rb_fstring(parse_string_encoded((pm_node_t *)str_node, &str_node->unescaped, parser)); - PM_COMPILE_NOT_POPPED(call_node->receiver); - ADD_INSN2(ret, &dummy_line_node, opt_aref_with, str, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); - RB_OBJ_WRITTEN(iseq, Qundef, str); - if (popped) { - ADD_INSN(ret, &dummy_line_node, pop); - } - } - else if (method_id == idASET && - !PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) && - call_node->arguments != NULL && - PM_NODE_TYPE_P((pm_node_t *)call_node->arguments, PM_ARGUMENTS_NODE) && - ((pm_arguments_node_t *)call_node->arguments)->arguments.size == 2 && - PM_NODE_TYPE_P(((pm_arguments_node_t *)call_node->arguments)->arguments.nodes[0], PM_STRING_NODE) && - call_node->block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && - ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - pm_string_node_t *str_node = (pm_string_node_t *)((pm_arguments_node_t *)call_node->arguments)->arguments.nodes[0]; - VALUE str = rb_fstring(parse_string_encoded((pm_node_t *)str_node, &str_node->unescaped, parser)); - PM_COMPILE_NOT_POPPED(call_node->receiver); - PM_COMPILE_NOT_POPPED(((pm_arguments_node_t *)call_node->arguments)->arguments.nodes[1]); - if (!popped) { - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + switch (method_id) { + case idUMinus: { + if (pm_opt_str_freeze_p(iseq, call_node)) { + VALUE value = rb_fstring(parse_string_encoded(call_node->receiver, &((const pm_string_node_t * )call_node->receiver)->unescaped, parser)); + ADD_INSN2(ret, &dummy_line_node, opt_str_uminus, value, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); + return; } - ADD_INSN2(ret, &dummy_line_node, opt_aset_with, str, new_callinfo(iseq, idASET, 2, 0, NULL, FALSE)); - RB_OBJ_WRITTEN(iseq, Qundef, str); - ADD_INSN(ret, &dummy_line_node, pop); - } - else { - if ((node->flags & PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) && !popped) { - PM_PUTNIL; + break; + } + case idFreeze: { + if (pm_opt_str_freeze_p(iseq, call_node)) { + VALUE value = rb_fstring(parse_string_encoded(call_node->receiver, &((const pm_string_node_t * )call_node->receiver)->unescaped, parser)); + ADD_INSN2(ret, &dummy_line_node, opt_str_freeze, value, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); + return; } + break; + } + case idAREF: { + if (pm_opt_aref_with_p(iseq, call_node)) { + const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[0]; + VALUE value = rb_fstring(parse_string_encoded((const pm_node_t *) string, &string->unescaped, parser)); - if (call_node->receiver == NULL) { - PM_PUTSELF; + PM_COMPILE_NOT_POPPED(call_node->receiver); + ADD_INSN2(ret, &dummy_line_node, opt_aref_with, value, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); + + if (popped) { + ADD_INSN(ret, &dummy_line_node, pop); + } + return; } - else { + break; + } + case idASET: { + if (pm_opt_aset_with_p(iseq, call_node)) { + const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[0]; + VALUE value = rb_fstring(parse_string_encoded((const pm_node_t *) string, &string->unescaped, parser)); + PM_COMPILE_NOT_POPPED(call_node->receiver); + PM_COMPILE_NOT_POPPED(((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[1]); + + if (!popped) { + ADD_INSN(ret, &dummy_line_node, swap); + ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + } + + ADD_INSN2(ret, &dummy_line_node, opt_aset_with, value, new_callinfo(iseq, idASET, 2, 0, NULL, FALSE)); + ADD_INSN(ret, &dummy_line_node, pop); + return; } - pm_compile_call(iseq, call_node, ret, popped, scope_node, method_id, start); + break; + } + } + + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) && !popped) { + PM_PUTNIL; + } + + if (call_node->receiver == NULL) { + PM_PUTSELF; + } + else { + PM_COMPILE_NOT_POPPED(call_node->receiver); } + pm_compile_call(iseq, call_node, ret, popped, scope_node, method_id, start); return; } case PM_CALL_AND_WRITE_NODE: { From 54295ba5e18818578d9df2ddbec7bebb8c4ec3a7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 10:14:27 -0500 Subject: [PATCH 080/142] [PRISM] Compile constant reads using opt_getconstant_path --- prism/util/pm_constant_pool.c | 2 +- prism/util/pm_constant_pool.h | 2 +- prism_compile.c | 131 ++++++++++++++++++---------------- prism_compile.h | 4 +- 4 files changed, 72 insertions(+), 67 deletions(-) diff --git a/prism/util/pm_constant_pool.c b/prism/util/pm_constant_pool.c index 2b607293ad1be9..cda76596fd7690 100644 --- a/prism/util/pm_constant_pool.c +++ b/prism/util/pm_constant_pool.c @@ -186,7 +186,7 @@ pm_constant_pool_id_to_constant(const pm_constant_pool_t *pool, pm_constant_id_t * the constant is not found. */ pm_constant_id_t -pm_constant_pool_find(pm_constant_pool_t *pool, const uint8_t *start, size_t length) { +pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size_t length) { assert(is_power_of_two(pool->capacity)); const uint32_t mask = pool->capacity - 1; diff --git a/prism/util/pm_constant_pool.h b/prism/util/pm_constant_pool.h index 086843c954ee46..bf049ec593bfbf 100644 --- a/prism/util/pm_constant_pool.h +++ b/prism/util/pm_constant_pool.h @@ -163,7 +163,7 @@ pm_constant_t * pm_constant_pool_id_to_constant(const pm_constant_pool_t *pool, * @param length The length of the constant. * @return The id of the constant. */ -pm_constant_id_t pm_constant_pool_find(pm_constant_pool_t *pool, const uint8_t *start, size_t length); +pm_constant_id_t pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size_t length); /** * Insert a constant into a constant pool that is a slice of a source string. diff --git a/prism_compile.c b/prism_compile.c index eedfa7d3aa9e94..3fd86b14b15c45 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -470,9 +470,7 @@ pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_no static void pm_compile_logical(rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_node_t *cond, LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node) { - pm_parser_t *parser = scope_node->parser; - pm_newline_list_t newline_list = parser->newline_list; - int lineno = (int)pm_newline_list_line_column(&newline_list, cond->location.start).line; + int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, cond->location.start).line; NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); DECL_ANCHOR(seq); @@ -550,9 +548,7 @@ static void pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_node_t *cond, LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node) { - pm_parser_t *parser = scope_node->parser; - pm_newline_list_t newline_list = parser->newline_list; - int lineno = (int) pm_newline_list_line_column(&newline_list, cond->location.start).line; + int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, cond->location.start).line; NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); again: @@ -731,7 +727,7 @@ pm_compile_while(rb_iseq_t *iseq, int lineno, pm_node_flags_t flags, enum pm_nod } static int -pm_interpolated_node_compile(pm_node_list_t *parts, rb_iseq_t *iseq, NODE dummy_line_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, pm_parser_t *parser) +pm_interpolated_node_compile(pm_node_list_t *parts, rb_iseq_t *iseq, NODE dummy_line_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, const pm_parser_t *parser) { int number_of_items_pushed = 0; size_t parts_size = parts->size; @@ -859,7 +855,7 @@ pm_constant_id_lookup(pm_scope_node_t *scope_node, pm_constant_id_t constant_id) } static rb_iseq_t * -pm_new_child_iseq(rb_iseq_t *iseq, pm_scope_node_t *node, pm_parser_t *parser, +pm_new_child_iseq(rb_iseq_t *iseq, pm_scope_node_t *node, const pm_parser_t *parser, VALUE name, const rb_iseq_t *parent, enum rb_iseq_type type, int line_no) { debugs("[new_child_iseq]> ---------------------------------------\n"); @@ -1033,7 +1029,7 @@ pm_arg_compile_keyword_hash_node(pm_keyword_hash_node_t *node, rb_iseq_t *iseq, // This is details. Users should call pm_setup_args() instead. static int -pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, const bool has_regular_blockarg, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node, pm_parser_t *parser) +pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, const bool has_regular_blockarg, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node, const pm_parser_t *parser) { int orig_argc = 0; bool has_splat = false; @@ -1249,7 +1245,7 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // Compile the argument parts of a call static int -pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node, pm_parser_t *parser) +pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node, const pm_parser_t *parser) { if (block && PM_NODE_TYPE_P(block, PM_BLOCK_ARGUMENT_NODE)) { // We compile the `&block_arg` expression first and stitch it later @@ -2441,7 +2437,7 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t // Generate a scope node from the given node. void -pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous, pm_parser_t *parser) +pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous, const pm_parser_t *parser) { scope->base.type = PM_SCOPE_NODE; scope->base.location.start = node->location.start; @@ -2912,13 +2908,11 @@ pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *con static void pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, ID method_id, LABEL *start) { - pm_parser_t *parser = scope_node->parser; - pm_newline_list_t newline_list = parser->newline_list; - + const pm_parser_t *parser = scope_node->parser; const uint8_t *call_start = call_node->message_loc.start; if (call_start == NULL) call_start = call_node->base.location.start; - int lineno = (int) pm_newline_list_line_column(&newline_list, call_start).line; + int lineno = (int) pm_newline_list_line_column(&parser->newline_list, call_start).line; NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); LABEL *else_label = NEW_LABEL(lineno); @@ -2940,7 +2934,7 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c pm_scope_node_t next_scope_node; pm_scope_node_init(call_node->block, &next_scope_node, scope_node, parser); - int block_lineno = (int) pm_newline_list_line_column(&newline_list, call_node->block->location.start).line; + int block_lineno = (int) pm_newline_list_line_column(&parser->newline_list, call_node->block->location.start).line; block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, block_lineno); pm_scope_node_destroy(&next_scope_node); @@ -3763,7 +3757,7 @@ static void pm_compile_rescue(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *const ret, int lineno, bool popped, pm_scope_node_t *scope_node) { NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - pm_parser_t *parser = scope_node->parser; + const pm_parser_t *parser = scope_node->parser; LABEL *lstart = NEW_LABEL(lineno); LABEL *lend = NEW_LABEL(lineno); LABEL *lcont = NEW_LABEL(lineno); @@ -3812,7 +3806,7 @@ static void pm_compile_ensure(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *const ret, int lineno, bool popped, pm_scope_node_t *scope_node) { NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - pm_parser_t *parser = scope_node->parser; + const pm_parser_t *parser = scope_node->parser; LABEL *estart = NEW_LABEL(lineno); LABEL *eend = NEW_LABEL(lineno); @@ -3929,6 +3923,28 @@ pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) ); } +/** + * Compile the instructions necessary to read a constant, based on the options + * of the current iseq. + */ +static inline void +pm_compile_constant_read(rb_iseq_t *iseq, VALUE name, const pm_location_t *name_loc, LINK_ANCHOR *const ret, const pm_scope_node_t *scope_node) +{ + int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, name_loc->start).line; + NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + + if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { + ISEQ_BODY(iseq)->ic_size++; + VALUE segments = rb_ary_new_from_args(1, name); + ADD_INSN1(ret, &dummy_line_node, opt_getconstant_path, segments); + } + else { + PM_PUTNIL; + ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); + ADD_INSN1(ret, &dummy_line_node, getconstant, name); + } +} + /* * Compiles a prism node into instruction sequences * @@ -3942,9 +3958,8 @@ pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - pm_parser_t *parser = scope_node->parser; - pm_newline_list_t newline_list = parser->newline_list; - int lineno = (int)pm_newline_list_line_column(&newline_list, node->location.start).line; + const pm_parser_t *parser = scope_node->parser; + int lineno = (int) pm_newline_list_line_column(&parser->newline_list, node->location.start).line; NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); if (node->flags & PM_NODE_FLAG_NEWLINE && ISEQ_COMPILE_DATA(iseq)->last_line != lineno) { @@ -4889,90 +4904,80 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CONSTANT_READ_NODE: { - pm_constant_read_node_t *constant_read_node = (pm_constant_read_node_t *) node; - PM_PUTNIL; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, ID2SYM(pm_constant_id_lookup(scope_node, constant_read_node->name))); + // Foo + // ^^^ + const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + pm_compile_constant_read(iseq, name, &cast->base.location, ret, scope_node); + PM_POP_IF_POPPED; return; } case PM_CONSTANT_AND_WRITE_NODE: { - pm_constant_and_write_node_t *constant_and_write_node = (pm_constant_and_write_node_t*) node; - + // Foo &&= bar + // ^^^^^^^^^^^ + const pm_constant_and_write_node_t *cast = (const pm_constant_and_write_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); LABEL *end_label = NEW_LABEL(lineno); - VALUE constant_name = ID2SYM(pm_constant_id_lookup(scope_node, constant_and_write_node->name)); + pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); - PM_PUTNIL; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, constant_name); PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); - PM_POP_UNLESS_POPPED; - PM_COMPILE_NOT_POPPED(constant_and_write_node->value); - + PM_COMPILE_NOT_POPPED(cast->value); PM_DUP_UNLESS_POPPED; ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, constant_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); ADD_LABEL(ret, end_label); return; } case PM_CONSTANT_OPERATOR_WRITE_NODE: { - pm_constant_operator_write_node_t *constant_operator_write_node = (pm_constant_operator_write_node_t*) node; + // Foo += bar + // ^^^^^^^^^^ + const pm_constant_operator_write_node_t *cast = (const pm_constant_operator_write_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); - ID constant_name = pm_constant_id_lookup(scope_node, constant_operator_write_node->name); - PM_PUTNIL; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, ID2SYM(constant_name)); - - PM_COMPILE_NOT_POPPED(constant_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, constant_operator_write_node->operator); - - int flags = VM_CALL_ARGS_SIMPLE; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(flags)); + pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); + PM_COMPILE_NOT_POPPED(cast->value); + ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); PM_DUP_UNLESS_POPPED; ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, ID2SYM(constant_name)); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); return; } case PM_CONSTANT_OR_WRITE_NODE: { - pm_constant_or_write_node_t *constant_or_write_node = (pm_constant_or_write_node_t*) node; - - LABEL *set_label= NEW_LABEL(lineno); + // Foo ||= bar + // ^^^^^^^^^^^ + const pm_constant_or_write_node_t *cast = (const pm_constant_or_write_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + LABEL *set_label = NEW_LABEL(lineno); LABEL *end_label = NEW_LABEL(lineno); PM_PUTNIL; - VALUE constant_name = ID2SYM(pm_constant_id_lookup(scope_node, constant_or_write_node->name)); - - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST), constant_name, Qtrue); - + ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST), name, Qtrue); ADD_INSNL(ret, &dummy_line_node, branchunless, set_label); - PM_PUTNIL; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, constant_name); + pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); - PM_POP_UNLESS_POPPED; ADD_LABEL(ret, set_label); - PM_COMPILE_NOT_POPPED(constant_or_write_node->value); + PM_COMPILE_NOT_POPPED(cast->value); PM_DUP_UNLESS_POPPED; - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, constant_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); ADD_LABEL(ret, end_label); return; diff --git a/prism_compile.h b/prism_compile.h index fe96146fed1675..2080db77393031 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -21,7 +21,7 @@ typedef struct pm_scope_node { pm_node_t *parameters; pm_node_t *body; pm_constant_id_list_t locals; - pm_parser_t *parser; + const pm_parser_t *parser; // The size of the local table // on the iseq which includes @@ -32,7 +32,7 @@ typedef struct pm_scope_node { st_table *index_lookup_table; } pm_scope_node_t; -void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous, pm_parser_t *parser); +void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous, const pm_parser_t *parser); void pm_scope_node_destroy(pm_scope_node_t *scope_node); bool *rb_ruby_prism_ptr(void); From 3e03981f256f76a5f8ff4a9f56a57dd18f68aa14 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 10:59:01 -0500 Subject: [PATCH 081/142] [PRISM] Compile constant paths with optimizations --- prism_compile.c | 245 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 180 insertions(+), 65 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 3fd86b14b15c45..19a2d9db4d0b4b 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -846,7 +846,7 @@ pm_lookup_local_index(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, pm_con // these constant_id indexes to the CRuby IDs that they represent. // This helper method allows easy access to those IDs static ID -pm_constant_id_lookup(pm_scope_node_t *scope_node, pm_constant_id_t constant_id) +pm_constant_id_lookup(const pm_scope_node_t *scope_node, pm_constant_id_t constant_id) { if (constant_id < 1 || constant_id > scope_node->parser->constant_pool.size) { rb_bug("constant_id out of range: %u", (unsigned int)constant_id); @@ -3927,7 +3927,7 @@ pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) * Compile the instructions necessary to read a constant, based on the options * of the current iseq. */ -static inline void +static void pm_compile_constant_read(rb_iseq_t *iseq, VALUE name, const pm_location_t *name_loc, LINK_ANCHOR *const ret, const pm_scope_node_t *scope_node) { int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, name_loc->start).line; @@ -3945,6 +3945,86 @@ pm_compile_constant_read(rb_iseq_t *iseq, VALUE name, const pm_location_t *name_ } } +/** + * Returns a Ruby array of the parts of the constant path node if it is constant + * reads all of the way down. If it isn't, then Qnil is returned. + */ +static VALUE +pm_constant_path_parts(const pm_node_t *node, const pm_scope_node_t *scope_node) +{ + VALUE parts = rb_ary_new(); + + while (true) { + switch (PM_NODE_TYPE(node)) { + case PM_CONSTANT_READ_NODE: { + const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + rb_ary_unshift(parts, name); + return parts; + } + case PM_CONSTANT_PATH_NODE: { + const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, ((const pm_constant_read_node_t *) cast->child)->name)); + + rb_ary_unshift(parts, name); + if (cast->parent == NULL) { + rb_ary_unshift(parts, ID2SYM(idNULL)); + return parts; + } + + node = cast->parent; + break; + } + default: + return Qnil; + } + } +} + +/** + * Compile a constant path into two sequences of instructions, one for the + * owning expression if there is one (prefix) and one for the constant reads + * (body). + */ +static void +pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const prefix, LINK_ANCHOR *const body, bool popped, pm_scope_node_t *scope_node) +{ + int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, node->location.start).line; + NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + + switch (PM_NODE_TYPE(node)) { + case PM_CONSTANT_READ_NODE: { + const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + ADD_INSN1(body, &dummy_line_node, putobject, Qtrue); + ADD_INSN1(body, &dummy_line_node, getconstant, name); + break; + } + case PM_CONSTANT_PATH_NODE: { + const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, ((const pm_constant_read_node_t *) cast->child)->name)); + + if (cast->parent == NULL) { + ADD_INSN(body, &dummy_line_node, pop); + ADD_INSN1(body, &dummy_line_node, putobject, rb_cObject); + ADD_INSN1(body, &dummy_line_node, putobject, Qtrue); + ADD_INSN1(body, &dummy_line_node, getconstant, name); + } + else { + pm_compile_constant_path(iseq, cast->parent, prefix, body, popped, scope_node); + ADD_INSN1(body, &dummy_line_node, putobject, Qfalse); + ADD_INSN1(body, &dummy_line_node, getconstant, name); + } + break; + } + default: + PM_COMPILE_INTO_ANCHOR(prefix, node); + break; + } +} + /* * Compiles a prism node into instruction sequences * @@ -4748,27 +4828,49 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CONSTANT_PATH_NODE: { - pm_constant_path_node_t *constant_path_node = (pm_constant_path_node_t*) node; - if (constant_path_node->parent) { - PM_COMPILE_NOT_POPPED(constant_path_node->parent); - } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + // Foo::Bar + // ^^^^^^^^ + VALUE parts; + + if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache && ((parts = pm_constant_path_parts(node, scope_node)) != Qnil)) { + ISEQ_BODY(iseq)->ic_size++; + ADD_INSN1(ret, &dummy_line_node, opt_getconstant_path, parts); } - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); + else { + int lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, node->location.start).line; + NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + + DECL_ANCHOR(prefix); + INIT_ANCHOR(prefix); - assert(PM_NODE_TYPE_P(constant_path_node->child, PM_CONSTANT_READ_NODE)); - pm_constant_read_node_t *child = (pm_constant_read_node_t *) constant_path_node->child; + DECL_ANCHOR(body); + INIT_ANCHOR(body); + + pm_compile_constant_path(iseq, node, prefix, body, popped, scope_node); + if (LIST_INSN_SIZE_ZERO(prefix)) { + ADD_INSN(ret, &dummy_line_node, putnil); + } + else { + ADD_SEQ(ret, prefix); + } + + ADD_SEQ(ret, body); + } - ADD_INSN1(ret, &dummy_line_node, getconstant, ID2SYM(pm_constant_id_lookup(scope_node, child->name))); PM_POP_IF_POPPED; return; } case PM_CONSTANT_PATH_AND_WRITE_NODE: { - pm_constant_path_and_write_node_t *constant_path_and_write_node = (pm_constant_path_and_write_node_t*) node; + // Foo::Bar &&= baz + // ^^^^^^^^^^^^^^^^ + const pm_constant_path_and_write_node_t *cast = (const pm_constant_path_and_write_node_t*) node; + const pm_constant_path_node_t *target = cast->target; + + const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); LABEL *lfin = NEW_LABEL(lineno); - pm_constant_path_node_t *target = constant_path_and_write_node->target; if (target->parent) { PM_COMPILE_NOT_POPPED(target->parent); } @@ -4776,18 +4878,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); } - pm_constant_read_node_t *child = (pm_constant_read_node_t *)target->child; - VALUE child_name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - PM_DUP; ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, child_name); + ADD_INSN1(ret, &dummy_line_node, getconstant, name); PM_DUP_UNLESS_POPPED; ADD_INSNL(ret, &dummy_line_node, branchunless, lfin); PM_POP_UNLESS_POPPED; - PM_COMPILE_NOT_POPPED(constant_path_and_write_node->value); + PM_COMPILE_NOT_POPPED(cast->value); if (popped) { ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); @@ -4797,7 +4896,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_SWAP; } - ADD_INSN1(ret, &dummy_line_node, setconstant, child_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); ADD_LABEL(ret, lfin); PM_SWAP_UNLESS_POPPED; @@ -4806,12 +4905,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CONSTANT_PATH_OR_WRITE_NODE: { - pm_constant_path_or_write_node_t *constant_path_or_write_node = (pm_constant_path_or_write_node_t*) node; + // Foo::Bar ||= baz + // ^^^^^^^^^^^^^^^^ + const pm_constant_path_or_write_node_t *cast = (const pm_constant_path_or_write_node_t *) node; + const pm_constant_path_node_t *target = cast->target; + + const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); LABEL *lassign = NEW_LABEL(lineno); LABEL *lfin = NEW_LABEL(lineno); - pm_constant_path_node_t *target = constant_path_or_write_node->target; if (target->parent) { PM_COMPILE_NOT_POPPED(target->parent); } @@ -4819,23 +4923,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); } - pm_constant_read_node_t *child = (pm_constant_read_node_t *)target->child; - VALUE child_name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - PM_DUP; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST_FROM), child_name, Qtrue); + ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST_FROM), name, Qtrue); ADD_INSNL(ret, &dummy_line_node, branchunless, lassign); PM_DUP; ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, child_name); + ADD_INSN1(ret, &dummy_line_node, getconstant, name); PM_DUP_UNLESS_POPPED; ADD_INSNL(ret, &dummy_line_node, branchif, lfin); PM_POP_UNLESS_POPPED; ADD_LABEL(ret, lassign); - PM_COMPILE_NOT_POPPED(constant_path_or_write_node->value); + PM_COMPILE_NOT_POPPED(cast->value); if (popped) { ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); @@ -4845,7 +4946,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_SWAP; } - ADD_INSN1(ret, &dummy_line_node, setconstant, child_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); ADD_LABEL(ret, lfin); PM_SWAP_UNLESS_POPPED; @@ -4854,9 +4955,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CONSTANT_PATH_OPERATOR_WRITE_NODE: { - pm_constant_path_operator_write_node_t *constant_path_operator_write_node = (pm_constant_path_operator_write_node_t*) node; + // Foo::Bar += baz + // ^^^^^^^^^^^^^^^ + const pm_constant_path_operator_write_node_t *cast = (const pm_constant_path_operator_write_node_t *) node; + const pm_constant_path_node_t *target = cast->target; + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); + + const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - pm_constant_path_node_t *target = constant_path_operator_write_node->target; if (target->parent) { PM_COMPILE_NOT_POPPED(target->parent); } @@ -4866,13 +4973,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_DUP; ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); + ADD_INSN1(ret, &dummy_line_node, getconstant, name); - pm_constant_read_node_t *child = (pm_constant_read_node_t *)target->child; - VALUE child_name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - ADD_INSN1(ret, &dummy_line_node, getconstant, child_name); - - PM_COMPILE_NOT_POPPED(constant_path_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, constant_path_operator_write_node->operator); + PM_COMPILE_NOT_POPPED(cast->value); ADD_CALL(ret, &dummy_line_node, method_id, INT2FIX(1)); PM_SWAP; @@ -4881,26 +4984,34 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_SWAP; } - ADD_INSN1(ret, &dummy_line_node, setconstant, child_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); return; } case PM_CONSTANT_PATH_WRITE_NODE: { - pm_constant_path_write_node_t *constant_path_write_node = (pm_constant_path_write_node_t*) node; - if (constant_path_write_node->target->parent) { - PM_COMPILE_NOT_POPPED((pm_node_t *)constant_path_write_node->target->parent); + // Foo::Bar = 1 + // ^^^^^^^^^^^^ + const pm_constant_path_write_node_t *cast = (const pm_constant_path_write_node_t *) node; + const pm_constant_path_node_t *target = cast->target; + + const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); + + if (target->parent) { + PM_COMPILE_NOT_POPPED((pm_node_t *) target->parent); } else { ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); } - PM_COMPILE_NOT_POPPED(constant_path_write_node->value); + + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) { PM_SWAP; ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); } + PM_SWAP; - VALUE constant_name = ID2SYM(pm_constant_id_lookup(scope_node, - ((pm_constant_read_node_t *)constant_path_write_node->target->child)->name)); - ADD_INSN1(ret, &dummy_line_node, setconstant, constant_name); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); return; } case PM_CONSTANT_READ_NODE: { @@ -4936,24 +5047,6 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } - case PM_CONSTANT_OPERATOR_WRITE_NODE: { - // Foo += bar - // ^^^^^^^^^^ - const pm_constant_operator_write_node_t *cast = (const pm_constant_operator_write_node_t *) node; - VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); - ID method_id = pm_constant_id_lookup(scope_node, cast->operator); - - pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); - - PM_COMPILE_NOT_POPPED(cast->value); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); - PM_DUP_UNLESS_POPPED; - - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, name); - - return; - } case PM_CONSTANT_OR_WRITE_NODE: { // Foo ||= bar // ^^^^^^^^^^^ @@ -4982,14 +5075,36 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } + case PM_CONSTANT_OPERATOR_WRITE_NODE: { + // Foo += bar + // ^^^^^^^^^^ + const pm_constant_operator_write_node_t *cast = (const pm_constant_operator_write_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); + + pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); + + PM_COMPILE_NOT_POPPED(cast->value); + ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); + PM_DUP_UNLESS_POPPED; + + ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); + + return; + } case PM_CONSTANT_WRITE_NODE: { - pm_constant_write_node_t *constant_write_node = (pm_constant_write_node_t *) node; - PM_COMPILE_NOT_POPPED(constant_write_node->value); + // Foo = 1 + // ^^^^^^^ + const pm_constant_write_node_t *cast = (const pm_constant_write_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + PM_COMPILE_NOT_POPPED(cast->value); PM_DUP_UNLESS_POPPED; ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, ID2SYM(pm_constant_id_lookup(scope_node, constant_write_node->name))); + ADD_INSN1(ret, &dummy_line_node, setconstant, name); + return; } case PM_DEF_NODE: { From 1936278461cf9aec1495596bf25a2963721f21ee Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 09:03:47 -0800 Subject: [PATCH 082/142] YJIT: Maintain MapToLocal that is just upgraded (#9876) --- yjit/src/core.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/yjit/src/core.rs b/yjit/src/core.rs index e46de01f2fcb93..b34a455b2a6b46 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -1947,6 +1947,9 @@ impl Context { let mut new_type = self.get_local_type(idx); new_type.upgrade(opnd_type); self.set_local_type(idx, new_type); + // Re-attach MapToLocal for this StackOpnd(idx). set_local_type() detaches + // all MapToLocal mappings, including the one we're upgrading here. + self.set_opnd_mapping(opnd, mapping); } } } @@ -3581,6 +3584,14 @@ mod tests { // TODO: write more tests for Context type diff } + #[test] + fn context_upgrade_local() { + let mut asm = Assembler::new(); + asm.stack_push_local(0); + asm.ctx.upgrade_opnd_type(StackOpnd(0), Type::Nil); + assert_eq!(Type::Nil, asm.ctx.get_opnd_type(StackOpnd(0))); + } + #[test] fn context_chain_depth() { let mut ctx = Context::default(); From 3ecfc3e33ef7db8e9f855490910077ac7ed13434 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 13:56:05 -0500 Subject: [PATCH 083/142] [PRISM] Support the DATA constant --- prism/prism.c | 1 + ruby.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/prism/prism.c b/prism/prism.c index 22503fd726f6e7..9df29a2de5a55d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -17791,6 +17791,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm .current = { .type = PM_TOKEN_EOF, .start = source, .end = source }, .next_start = NULL, .heredoc_end = NULL, + .data_loc = { .start = NULL, .end = NULL }, .comment_list = { 0 }, .magic_comment_list = { 0 }, .warning_list = { 0 }, diff --git a/ruby.c b/ruby.c index b0bce7216bb585..2e95b37173212d 100644 --- a/ruby.c +++ b/ruby.c @@ -2423,6 +2423,21 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } else { error = pm_parse_file(&result, opt->script_name); + + // If we found an __END__ marker, then we're going to define a + // global DATA constant that is a file object that can be read + // to read the contents after the marker. + if (error == Qnil && result.parser.data_loc.start != NULL) { + int xflag = opt->xflag; + VALUE file = open_load_file(opt->script_name, &xflag); + + size_t offset = result.parser.data_loc.start - result.parser.start + 7; + if ((result.parser.start + offset < result.parser.end) && result.parser.start[offset] == '\r') offset++; + if ((result.parser.start + offset < result.parser.end) && result.parser.start[offset] == '\n') offset++; + + rb_funcall(file, rb_intern("seek"), 2, LONG2NUM(offset), INT2FIX(SEEK_SET)); + rb_define_global_const("DATA", file); + } } if (error == Qnil) { From 4a40364c62ee4689bb95e6c26669a28861b6bc8b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 13:19:51 -0500 Subject: [PATCH 084/142] [PRISM] Run opt init before parsing --- ruby.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby.c b/ruby.c index 2e95b37173212d..4c9bf4349a6009 100644 --- a/ruby.c +++ b/ruby.c @@ -2407,6 +2407,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) const struct rb_block *base_block = toplevel_context(toplevel_binding); if ((*rb_ruby_prism_ptr())) { + ruby_opt_init(opt); pm_parse_result_t result = { 0 }; VALUE error; @@ -2441,7 +2442,6 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } if (error == Qnil) { - ruby_opt_init(opt); iseq = pm_iseq_new_main(&result.node, opt->script_name, path, vm_block_iseq(base_block), !(dump & DUMP_BIT(insns_without_opt))); pm_parse_result_free(&result); } From 5cbca9110c3d8cffc95c70a0c0c793fe8c1d3662 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 15:47:02 -0800 Subject: [PATCH 085/142] YJIT: Allow tracing a counted exit (#9890) * YJIT: Allow tracing a counted exit * Avoid clobbering caller-saved registers --- doc/yjit/yjit.md | 5 +++-- yjit/src/backend/ir.rs | 2 +- yjit/src/codegen.rs | 35 +++++++++++++++++++++++++---------- yjit/src/core.rs | 2 +- yjit/src/options.rs | 33 ++++++++++++++++++++++++++++----- yjit/src/stats.rs | 18 +++++++++++++----- 6 files changed, 71 insertions(+), 24 deletions(-) diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index e6446e3ed1b66b..c1b481e8ff661d 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -177,8 +177,9 @@ YJIT supports all command-line options supported by upstream CRuby, but also add It will cause all machine code to be discarded when the executable memory size limit is hit, meaning JIT compilation will then start over. This can allow you to use a lower executable memory size limit, but may cause a slight drop in performance when the limit is hit. - `--yjit-perf`: enable frame pointers and profiling with the `perf` tool -- `--yjit-trace-exits`: produce a Marshal dump of backtraces from specific exits. Automatically enables `--yjit-stats` -- `--yjit-trace-exits-sample-rate=N`: trace exit locations only every Nth occurrence +- `--yjit-trace-exits`: produce a Marshal dump of backtraces from all exits. Automatically enables `--yjit-stats` +- `--yjit-trace-exits=COUNTER`: produce a Marshal dump of backtraces from specified exits. Automatically enables `--yjit-stats` +- `--yjit-trace-exits-sample-rate=N`: trace exit locations only every Nth occurrence. Automatically enables `--yjit-trace-exits` Note that there is also an environment variable `RUBY_YJIT_ENABLE` which can be used to enable YJIT. This can be useful for some deployment scripts where specifying an extra command-line option to Ruby is not practical. diff --git a/yjit/src/backend/ir.rs b/yjit/src/backend/ir.rs index 02fe20a0a377a1..9cb089ba6b61a5 100644 --- a/yjit/src/backend/ir.rs +++ b/yjit/src/backend/ir.rs @@ -1117,7 +1117,7 @@ impl Assembler }; // Wrap a counter if needed - gen_counted_exit(side_exit, ocb, counter) + gen_counted_exit(side_exit_context.pc, side_exit, ocb, counter) } /// Create a new label instance that we can jump to diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index e5180ced164054..0d721422c9ba49 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -532,9 +532,9 @@ fn gen_exit(exit_pc: *mut VALUE, asm: &mut Assembler) { vec![Opnd::const_ptr(exit_pc as *const u8)] ); - // If --yjit-trace-exits option is enabled, record the exit stack - // while recording the side exits. - if get_option!(gen_trace_exits) { + // If --yjit-trace-exits is enabled, record the exit stack while recording + // the side exits. TraceExits::Counter is handled by gen_counted_exit(). + if get_option!(trace_exits) == Some(TraceExits::All) { asm.ccall( rb_yjit_record_exit_stack as *const u8, vec![Opnd::const_ptr(exit_pc as *const u8)] @@ -575,7 +575,7 @@ pub fn gen_outlined_exit(exit_pc: *mut VALUE, ctx: &Context, ocb: &mut OutlinedC } /// Get a side exit. Increment a counter in it if --yjit-stats is enabled. -pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Option) -> Option { +pub fn gen_counted_exit(exit_pc: *mut VALUE, side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Option) -> Option { // The counter is only incremented when stats are enabled if !get_option!(gen_stats) { return Some(side_exit); @@ -587,13 +587,16 @@ pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Optio let mut asm = Assembler::new(); - // Load the pointer into a register - asm_comment!(asm, "increment counter {}", counter.get_name()); - let ptr_reg = asm.load(Opnd::const_ptr(get_counter_ptr(&counter.get_name()) as *const u8)); - let counter_opnd = Opnd::mem(64, ptr_reg, 0); + // Increment a counter + gen_counter_incr(&mut asm, counter); - // Increment and store the updated value - asm.incr_counter(counter_opnd, Opnd::UImm(1)); + // Trace a counted exit if --yjit-trace-exits=counter is given. + // TraceExits::All is handled by gen_exit(). + if get_option!(trace_exits) == Some(TraceExits::CountedExit(counter)) { + with_caller_saved_temp_regs(&mut asm, |asm| { + asm.ccall(rb_yjit_record_exit_stack as *const u8, vec![Opnd::const_ptr(exit_pc as *const u8)]); + }); + } // Jump to the existing side exit asm.jmp(Target::CodePtr(side_exit)); @@ -602,6 +605,18 @@ pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Optio asm.compile(ocb, None).map(|(code_ptr, _)| code_ptr) } +/// Preserve caller-saved stack temp registers during the call of a given block +fn with_caller_saved_temp_regs(asm: &mut Assembler, block: F) -> R where F: FnOnce(&mut Assembler) -> R { + for ® in caller_saved_temp_regs() { + asm.cpush(Opnd::Reg(reg)); // save stack temps + } + let ret = block(asm); + for ® in caller_saved_temp_regs().rev() { + asm.cpop_into(Opnd::Reg(reg)); // restore stack temps + } + ret +} + // Ensure that there is an exit for the start of the block being compiled. // Block invalidation uses this exit. #[must_use] diff --git a/yjit/src/core.rs b/yjit/src/core.rs index b34a455b2a6b46..f7920de25d6cd4 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -2960,7 +2960,7 @@ pub fn gen_branch_stub_hit_trampoline(ocb: &mut OutlinedCb) -> Option { } /// Return registers to be pushed and popped on branch_stub_hit. -fn caller_saved_temp_regs() -> impl Iterator + DoubleEndedIterator { +pub fn caller_saved_temp_regs() -> impl Iterator + DoubleEndedIterator { let temp_regs = Assembler::get_temp_regs().iter(); let len = temp_regs.len(); // The return value gen_leave() leaves in C_RET_REG diff --git a/yjit/src/options.rs b/yjit/src/options.rs index 9a146512bb839e..a6f8b3c69e7bf2 100644 --- a/yjit/src/options.rs +++ b/yjit/src/options.rs @@ -1,5 +1,5 @@ use std::{ffi::{CStr, CString}, ptr::null, fs::File}; -use crate::backend::current::TEMP_REGS; +use crate::{backend::current::TEMP_REGS, stats::Counter}; use std::os::raw::{c_char, c_int, c_uint}; // Call threshold for small deployments and command-line apps @@ -48,7 +48,7 @@ pub struct Options { pub print_stats: bool, // Trace locations of exits - pub gen_trace_exits: bool, + pub trace_exits: Option, // how often to sample exit trace data pub trace_exits_sample_rate: usize, @@ -86,7 +86,7 @@ pub static mut OPTIONS: Options = Options { max_versions: 4, num_temp_regs: 5, gen_stats: false, - gen_trace_exits: false, + trace_exits: None, print_stats: true, trace_exits_sample_rate: 0, disable: false, @@ -112,6 +112,14 @@ static YJIT_OPTIONS: [(&str, &str); 9] = [ ("--yjit-trace-exits-sample-rate=num", "Trace exit locations only every Nth occurrence"), ]; +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TraceExits { + // Trace all exits + All, + // Trace a specific counted exit + CountedExit(Counter), +} + #[derive(Clone, PartialEq, Eq, Debug)] pub enum DumpDisasm { // Dump to stdout @@ -267,8 +275,23 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { return None; } }, - ("trace-exits", "") => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = 0 }, - ("trace-exits-sample-rate", sample_rate) => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = sample_rate.parse().unwrap(); }, + ("trace-exits", _) => unsafe { + OPTIONS.gen_stats = true; + OPTIONS.trace_exits = match opt_val { + "" => Some(TraceExits::All), + name => match Counter::get(name) { + Some(counter) => Some(TraceExits::CountedExit(counter)), + None => return None, + }, + }; + }, + ("trace-exits-sample-rate", sample_rate) => unsafe { + OPTIONS.gen_stats = true; + if OPTIONS.trace_exits.is_none() { + OPTIONS.trace_exits = Some(TraceExits::All); + } + OPTIONS.trace_exits_sample_rate = sample_rate.parse().unwrap(); + }, ("dump-insns", "") => unsafe { OPTIONS.dump_insns = true }, ("verify-ctx", "") => unsafe { OPTIONS.verify_ctx = true }, diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 08eb53bb5143f8..056bb41931f14b 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -128,7 +128,7 @@ impl YjitExitLocations { /// Initialize the yjit exit locations pub fn init() { // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } @@ -177,7 +177,7 @@ impl YjitExitLocations { } // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } @@ -219,6 +219,14 @@ macro_rules! make_counters { pub enum Counter { $($counter_name),+ } impl Counter { + /// Map a counter name string to a counter enum + pub fn get(name: &str) -> Option { + match name { + $( stringify!($counter_name) => { Some(Counter::$counter_name) } ),+ + _ => None, + } + } + /// Get a counter name string pub fn get_name(&self) -> String { match self { @@ -636,7 +644,7 @@ pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE, context: VALU /// to be enabled. #[no_mangle] pub extern "C" fn rb_yjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_some() { return Qtrue; } @@ -653,7 +661,7 @@ pub extern "C" fn rb_yjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> V } // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return Qnil; } @@ -834,7 +842,7 @@ pub extern "C" fn rb_yjit_record_exit_stack(_exit_pc: *const VALUE) } // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } From e2aa00ca669704daf689110ea71e5844e2bd0536 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 15:52:45 -0800 Subject: [PATCH 086/142] YJIT: Remove unnecessary casts for chain_depth (#9893) --- yjit/src/codegen.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 0d721422c9ba49..07155efd69234f 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2277,7 +2277,7 @@ fn jit_chain_guard( jit: &mut JITState, asm: &mut Assembler, ocb: &mut OutlinedCb, - depth_limit: i32, + depth_limit: u8, counter: Counter, ) { let target0_gen_fn = match jcc { @@ -2288,7 +2288,7 @@ fn jit_chain_guard( JCC_JO_MUL => BranchGenFn::JOMulToTarget0, }; - if (asm.ctx.get_chain_depth() as i32) < depth_limit { + if asm.ctx.get_chain_depth() < depth_limit { // Rewind Context to use the stack_size at the beginning of this instruction. let mut deeper = asm.ctx.with_stack_size(jit.stack_size_for_pc); deeper.increment_chain_depth(); @@ -2304,22 +2304,22 @@ fn jit_chain_guard( } // up to 8 different shapes for each -pub const GET_IVAR_MAX_DEPTH: i32 = 8; +pub const GET_IVAR_MAX_DEPTH: u8 = 8; // up to 8 different shapes for each -pub const SET_IVAR_MAX_DEPTH: i32 = 8; +pub const SET_IVAR_MAX_DEPTH: u8 = 8; // hashes and arrays -pub const OPT_AREF_MAX_CHAIN_DEPTH: i32 = 2; +pub const OPT_AREF_MAX_CHAIN_DEPTH: u8 = 2; // expandarray -pub const EXPANDARRAY_MAX_CHAIN_DEPTH: i32 = 4; +pub const EXPANDARRAY_MAX_CHAIN_DEPTH: u8 = 4; // up to 5 different methods for send -pub const SEND_MAX_DEPTH: i32 = 5; +pub const SEND_MAX_DEPTH: u8 = 5; // up to 20 different offsets for case-when -pub const CASE_WHEN_MAX_DEPTH: i32 = 20; +pub const CASE_WHEN_MAX_DEPTH: u8 = 20; pub const MAX_SPLAT_LENGTH: i32 = 127; @@ -2374,7 +2374,7 @@ fn gen_get_ivar( jit: &mut JITState, asm: &mut Assembler, ocb: &mut OutlinedCb, - max_chain_depth: i32, + max_chain_depth: u8, comptime_receiver: VALUE, ivar_name: ID, recv: Opnd, @@ -2401,7 +2401,7 @@ fn gen_get_ivar( // Check if the comptime receiver is a T_OBJECT let receiver_t_object = unsafe { RB_TYPE_P(comptime_receiver, RUBY_T_OBJECT) }; // Use a general C call at the last chain to avoid exits on megamorphic shapes - let megamorphic = asm.ctx.get_chain_depth() as i32 >= max_chain_depth; + let megamorphic = asm.ctx.get_chain_depth() >= max_chain_depth; if megamorphic { gen_counter_incr(asm, Counter::num_getivar_megamorphic); } @@ -2613,7 +2613,7 @@ fn gen_setinstancevariable( // Check if the comptime receiver is a T_OBJECT let receiver_t_object = unsafe { RB_TYPE_P(comptime_receiver, RUBY_T_OBJECT) }; // Use a general C call at the last chain to avoid exits on megamorphic shapes - let megamorphic = asm.ctx.get_chain_depth() as i32 >= SET_IVAR_MAX_DEPTH; + let megamorphic = asm.ctx.get_chain_depth() >= SET_IVAR_MAX_DEPTH; if megamorphic { gen_counter_incr(asm, Counter::num_setivar_megamorphic); } @@ -2865,7 +2865,7 @@ fn gen_definedivar( // Specialize base on compile time values let comptime_receiver = jit.peek_at_self(); - if comptime_receiver.shape_too_complex() || asm.ctx.get_chain_depth() as i32 >= GET_IVAR_MAX_DEPTH { + if comptime_receiver.shape_too_complex() || asm.ctx.get_chain_depth() >= GET_IVAR_MAX_DEPTH { // Fall back to calling rb_ivar_defined // Save the PC and SP because the callee may allocate @@ -4321,7 +4321,7 @@ fn jit_guard_known_klass( obj_opnd: Opnd, insn_opnd: YARVOpnd, sample_instance: VALUE, - max_chain_depth: i32, + max_chain_depth: u8, counter: Counter, ) { let val_type = asm.ctx.get_opnd_type(insn_opnd); @@ -7654,7 +7654,7 @@ fn gen_send_general( gen_counter_incr(asm, Counter::num_send_polymorphic); } // If megamorphic, let the caller fallback to dynamic dispatch - if asm.ctx.get_chain_depth() as i32 >= SEND_MAX_DEPTH { + if asm.ctx.get_chain_depth() >= SEND_MAX_DEPTH { gen_counter_incr(asm, Counter::send_megamorphic); return None; } @@ -8173,7 +8173,7 @@ fn gen_invokeblock_specialized( } // Fallback to dynamic dispatch if this callsite is megamorphic - if asm.ctx.get_chain_depth() as i32 >= SEND_MAX_DEPTH { + if asm.ctx.get_chain_depth() >= SEND_MAX_DEPTH { gen_counter_incr(asm, Counter::invokeblock_megamorphic); return None; } @@ -8354,7 +8354,7 @@ fn gen_invokesuper_specialized( }; // Fallback to dynamic dispatch if this callsite is megamorphic - if asm.ctx.get_chain_depth() as i32 >= SEND_MAX_DEPTH { + if asm.ctx.get_chain_depth() >= SEND_MAX_DEPTH { gen_counter_incr(asm, Counter::invokesuper_megamorphic); return None; } From 5d32e328d90e435ceb5d1259438157ab5d2a4608 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 16:53:01 -0800 Subject: [PATCH 087/142] YJIT: Refactor recv_known_class to Option (#9895) --- yjit/src/codegen.rs | 97 ++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 07155efd69234f..afcaf544063eba 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4492,7 +4492,7 @@ fn jit_rb_obj_not( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { let recv_opnd = asm.ctx.get_opnd_type(StackOpnd(0)); @@ -4527,7 +4527,7 @@ fn jit_rb_true( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "nil? == true"); asm.stack_pop(1); @@ -4545,7 +4545,7 @@ fn jit_rb_false( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "nil? == false"); asm.stack_pop(1); @@ -4563,14 +4563,14 @@ fn jit_rb_kernel_is_a( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - known_recv_class: *const VALUE, + known_recv_class: Option, ) -> bool { if argc != 1 { return false; } // If this is a super call we might not know the class - if known_recv_class.is_null() { + if known_recv_class.is_none() { return false; } @@ -4616,14 +4616,14 @@ fn jit_rb_kernel_instance_of( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - known_recv_class: *const VALUE, + known_recv_class: Option, ) -> bool { if argc != 1 { return false; } // If this is a super call we might not know the class - if known_recv_class.is_null() { + if known_recv_class.is_none() { return false; } @@ -4681,7 +4681,7 @@ fn jit_rb_mod_eqq( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if argc != 1 { return false; @@ -4715,7 +4715,7 @@ fn jit_rb_obj_equal( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "equal?"); let obj1 = asm.stack_pop(1); @@ -4739,7 +4739,7 @@ fn jit_rb_obj_not_equal( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { gen_equality_specialized(jit, asm, ocb, false) == Some(true) } @@ -4753,7 +4753,7 @@ fn jit_rb_int_equal( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Check that both operands are fixnums guard_two_fixnums(jit, asm, ocb); @@ -4778,7 +4778,7 @@ fn jit_rb_int_succ( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Guard the receiver is fixnum let recv_type = asm.ctx.get_opnd_type(StackOpnd(0)); @@ -4808,7 +4808,7 @@ fn jit_rb_int_div( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if asm.ctx.two_fixnums_on_stack(jit) != Some(true) { return false; @@ -4842,7 +4842,7 @@ fn jit_rb_int_lshift( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if asm.ctx.two_fixnums_on_stack(jit) != Some(true) { return false; @@ -4907,7 +4907,7 @@ fn jit_rb_int_rshift( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if asm.ctx.two_fixnums_on_stack(jit) != Some(true) { return false; @@ -4960,7 +4960,7 @@ fn jit_rb_int_xor( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if asm.ctx.two_fixnums_on_stack(jit) != Some(true) { return false; @@ -4987,7 +4987,7 @@ fn jit_rb_int_aref( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if argc != 1 { return false; @@ -5016,7 +5016,7 @@ fn jit_rb_float_plus( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Guard obj is Fixnum or Flonum to avoid rb_funcall on rb_num_coerce_bin let comptime_obj = jit.peek_at_stack(&asm.ctx, 0); @@ -5060,7 +5060,7 @@ fn jit_rb_float_minus( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Guard obj is Fixnum or Flonum to avoid rb_funcall on rb_num_coerce_bin let comptime_obj = jit.peek_at_stack(&asm.ctx, 0); @@ -5104,7 +5104,7 @@ fn jit_rb_float_mul( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Guard obj is Fixnum or Flonum to avoid rb_funcall on rb_num_coerce_bin let comptime_obj = jit.peek_at_stack(&asm.ctx, 0); @@ -5148,7 +5148,7 @@ fn jit_rb_float_div( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // Guard obj is Fixnum or Flonum to avoid rb_funcall on rb_num_coerce_bin let comptime_obj = jit.peek_at_stack(&asm.ctx, 0); @@ -5193,7 +5193,7 @@ fn jit_rb_str_uplus( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { if argc != 0 { @@ -5237,7 +5237,7 @@ fn jit_rb_str_length( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "String#length"); extern "C" { @@ -5264,7 +5264,7 @@ fn jit_rb_str_bytesize( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "String#bytesize"); @@ -5296,7 +5296,7 @@ fn jit_rb_str_getbyte( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "String#getbyte"); extern "C" { @@ -5329,9 +5329,9 @@ fn jit_rb_str_to_s( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - known_recv_class: *const VALUE, + known_recv_class: Option, ) -> bool { - if !known_recv_class.is_null() && unsafe { *known_recv_class == rb_cString } { + if unsafe { known_recv_class == Some(rb_cString) } { asm_comment!(asm, "to_s on plain string"); // The method returns the receiver, which is already on the stack. // No stack movement. @@ -5349,7 +5349,7 @@ fn jit_rb_str_empty_p( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { let recv_opnd = asm.stack_pop(1); @@ -5379,7 +5379,7 @@ fn jit_rb_str_concat( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { // The << operator can accept integer codepoints for characters // as the argument. We only specially optimise string arguments. @@ -5450,7 +5450,7 @@ fn jit_rb_ary_empty_p( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { let array_opnd = asm.stack_pop(1); let array_reg = asm.load(array_opnd); @@ -5474,7 +5474,7 @@ fn jit_rb_ary_length( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { let array_opnd = asm.stack_pop(1); let array_reg = asm.load(array_opnd); @@ -5498,7 +5498,7 @@ fn jit_rb_ary_push( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "Array#<<"); @@ -5524,7 +5524,7 @@ fn jit_rb_hash_empty_p( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "Hash#empty?"); @@ -5544,18 +5544,17 @@ fn jit_obj_respond_to( _cme: *const rb_callable_method_entry_t, _block: Option, argc: i32, - known_recv_class: *const VALUE, + known_recv_class: Option, ) -> bool { // respond_to(:sym) or respond_to(:sym, true) if argc != 1 && argc != 2 { return false; } - if known_recv_class.is_null() { - return false; - } - - let recv_class = unsafe { *known_recv_class }; + let recv_class = match known_recv_class { + Some(class) => class, + None => return false, + }; // Get the method_id from compile time. We will later add a guard against it. let mid_sym = jit.peek_at_stack(&asm.ctx, (argc - 1) as isize); @@ -5651,7 +5650,7 @@ fn jit_rb_f_block_given_p( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm.stack_pop(1); let out_opnd = asm.stack_push(Type::UnknownImm); @@ -5690,7 +5689,7 @@ fn jit_thread_s_current( _cme: *const rb_callable_method_entry_t, _block: Option, _argc: i32, - _known_recv_class: *const VALUE, + _known_recv_class: Option, ) -> bool { asm_comment!(asm, "Thread.current"); asm.stack_pop(1); @@ -5904,7 +5903,7 @@ fn gen_send_cfunc( ci: *const rb_callinfo, cme: *const rb_callable_method_entry_t, block: Option, - recv_known_klass: *const VALUE, + recv_known_class: Option, flags: u32, argc: i32, ) -> Option { @@ -5950,7 +5949,7 @@ fn gen_send_cfunc( let codegen_p = lookup_cfunc_codegen(unsafe { (*cme).def }); let expected_stack_after = asm.ctx.get_stack_size() as i32 - argc; if let Some(known_cfunc_codegen) = codegen_p { - if known_cfunc_codegen(jit, asm, ocb, ci, cme, block, argc, recv_known_klass) { + if known_cfunc_codegen(jit, asm, ocb, ci, cme, block, argc, recv_known_class) { assert_eq!(expected_stack_after, asm.ctx.get_stack_size() as i32); gen_counter_incr(asm, Counter::num_send_cfunc_inline); // cfunc codegen generated code. Terminate the block so @@ -5968,8 +5967,8 @@ fn gen_send_cfunc( // Assemble the method name string let mid = unsafe { vm_ci_mid(ci) }; - let class_name = if recv_known_klass != ptr::null() { - unsafe { cstr_to_rust_string(rb_class2name(*recv_known_klass)) }.unwrap() + let class_name = if let Some(class) = recv_known_class { + unsafe { cstr_to_rust_string(rb_class2name(class)) }.unwrap() } else { "Unknown".to_string() }; @@ -7726,7 +7725,7 @@ fn gen_send_general( ci, cme, block, - &comptime_recv_klass, + Some(comptime_recv_klass), flags, argc, ); @@ -8463,7 +8462,7 @@ fn gen_invokesuper_specialized( gen_send_iseq(jit, asm, ocb, iseq, ci, frame_type, None, cme, Some(block), ci_flags, argc, None) } VM_METHOD_TYPE_CFUNC => { - gen_send_cfunc(jit, asm, ocb, ci, cme, Some(block), ptr::null(), ci_flags, argc) + gen_send_cfunc(jit, asm, ocb, ci, cme, Some(block), None, ci_flags, argc) } _ => unreachable!(), } @@ -9328,7 +9327,7 @@ fn get_gen_fn(opcode: VALUE) -> Option { } // Return true when the codegen function generates code. -// known_recv_klass is non-NULL when the caller has used jit_guard_known_klass(). +// known_recv_class has Some value when the caller has used jit_guard_known_klass(). // See yjit_reg_method(). type MethodGenFn = fn( jit: &mut JITState, @@ -9338,7 +9337,7 @@ type MethodGenFn = fn( cme: *const rb_callable_method_entry_t, block: Option, argc: i32, - known_recv_class: *const VALUE, + known_recv_class: Option, ) -> bool; /// Methods for generating code for hardcoded (usually C) methods From 2a57e6e6edbd848b5646a762e9351912c857706f Mon Sep 17 00:00:00 2001 From: Petrik Date: Thu, 8 Feb 2024 21:58:56 +0100 Subject: [PATCH 088/142] [ruby/rdoc] Don't document aliases with trailing `:nodoc` directive Attribute readers and writers can be marked as `:nodoc` to keep them undocumented: ```ruby attr_reader :name # :nodoc: ``` For aliases this behaviour should be the same: ```ruby alias_method :old :new # :nodoc: ``` https://github.com/ruby/rdoc/commit/30f14e8271 --- lib/rdoc/parser/ruby.rb | 6 ++++-- test/rdoc/test_rdoc_parser_ruby.rb | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index 2cc629ebdfeb81..85f1cd03913963 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -789,8 +789,10 @@ def parse_alias(context, single, tk, comment) al.line = line_no read_documentation_modifiers al, RDoc::ATTR_MODIFIERS - context.add_alias al - @stats.add_alias al + if al.document_self or not @track_visibility + context.add_alias al + @stats.add_alias al + end al end diff --git a/test/rdoc/test_rdoc_parser_ruby.rb b/test/rdoc/test_rdoc_parser_ruby.rb index 1f75ba7c6ef2b4..3e2a85ffba6a6b 100644 --- a/test/rdoc/test_rdoc_parser_ruby.rb +++ b/test/rdoc/test_rdoc_parser_ruby.rb @@ -3065,6 +3065,28 @@ def test_parse_statements_identifier_yields assert_nil m.params, 'Module parameter not removed' end + def test_parse_statements_nodoc_identifier_alias + klass = @top_level.add_class RDoc::NormalClass, 'Foo' + + util_parser "\nalias :old :new # :nodoc:" + + @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil + + assert_empty klass.aliases + assert_empty klass.unmatched_alias_lists + end + + def test_parse_statements_nodoc_identifier_alias_method + klass = @top_level.add_class RDoc::NormalClass, 'Foo' + + util_parser "\nalias_method :old :new # :nodoc:" + + @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil + + assert_empty klass.aliases + assert_empty klass.unmatched_alias_lists + end + def test_parse_statements_stopdoc_alias klass = @top_level.add_class RDoc::NormalClass, 'Foo' From 76f0eec20f5a58b79177c3b6c601b6e361db6da9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 8 Feb 2024 17:07:15 -0800 Subject: [PATCH 089/142] Fix a benchmark to avoid leaving a garbage file --- benchmark/realpath.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/benchmark/realpath.yml b/benchmark/realpath.yml index 90a029d5b982ba..6b6a4836b0df20 100644 --- a/benchmark/realpath.yml +++ b/benchmark/realpath.yml @@ -12,6 +12,9 @@ prelude: | relative_dir = 'b/c' absolute_dir = File.join(pwd, relative_dir) file_dir = 'c' +teardown: | + require 'fileutils' + FileUtils.rm_rf('b') benchmark: relative_nil: "f.realpath(relative, nil)" absolute_nil: "f.realpath(absolute, nil)" From 0923a98868d6c2bed00eb1d8fa2ddf98fb42a708 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 8 Feb 2024 18:15:08 +0900 Subject: [PATCH 090/142] Move clean-up after table rebuilding Suppress a false positive alert by CodeQL. --- st.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/st.c b/st.c index 33a05c3c4a37f3..4f0259a237325a 100644 --- a/st.c +++ b/st.c @@ -718,7 +718,9 @@ count_collision(const struct st_hash_type *type) #error "REBUILD_THRESHOLD should be >= 2" #endif -static void rebuild_table_with(st_table *new_tab, st_table *tab); +static void rebuild_table_with(st_table *const new_tab, st_table *const tab); +static void rebuild_move_table(st_table *const new_tab, st_table *const tab); +static void rebuild_cleanup(st_table *const tab); /* Rebuild table TAB. Rebuilding removes all deleted bins and entries and can change size of the table entries and bins arrays. @@ -744,11 +746,13 @@ rebuild_table(st_table *tab) new_tab = st_init_table_with_size(tab->type, 2 * tab->num_entries - 1); rebuild_table_with(new_tab, tab); + rebuild_move_table(new_tab, tab); } + rebuild_cleanup(tab); } static void -rebuild_table_with(st_table *new_tab, st_table *tab) +rebuild_table_with(st_table *const new_tab, st_table *const tab) { st_index_t i, ni; unsigned int size_ind; @@ -780,16 +784,24 @@ rebuild_table_with(st_table *new_tab, st_table *tab) new_tab->num_entries++; ni++; } - if (new_tab != tab) { - tab->entry_power = new_tab->entry_power; - tab->bin_power = new_tab->bin_power; - tab->size_ind = new_tab->size_ind; - free(tab->bins); - tab->bins = new_tab->bins; - free(tab->entries); - tab->entries = new_tab->entries; - free(new_tab); - } +} + +static void +rebuild_move_table(st_table *const new_tab, st_table *const tab) +{ + tab->entry_power = new_tab->entry_power; + tab->bin_power = new_tab->bin_power; + tab->size_ind = new_tab->size_ind; + free(tab->bins); + tab->bins = new_tab->bins; + free(tab->entries); + tab->entries = new_tab->entries; + free(new_tab); +} + +static void +rebuild_cleanup(st_table *const tab) +{ tab->entries_start = 0; tab->entries_bound = tab->num_entries; tab->rebuilds_num++; @@ -2319,6 +2331,8 @@ rb_st_compact_table(st_table *tab) /* Compaction: */ st_table *new_tab = st_init_table_with_size(tab->type, 2 * num); rebuild_table_with(new_tab, tab); + rebuild_move_table(new_tab, tab); + rebuild_cleanup(tab); } } From 50bcaa62868d806ae861c9dc3353e1f4e85a99f6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 12:17:31 +0900 Subject: [PATCH 091/142] [ruby/optparse] Escape backslashes https://github.com/ruby/optparse/commit/b14c2c644d --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ace57952f070db..ecf6adc45fc5f4 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1033,7 +1033,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: to << "#compdef #{name}\n" to << COMPSYS_HEADER visit(:compsys, {}, {}) {|o, d| - to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n] + to << %Q[ "#{o}[#{d.gsub(/[\\\"\[\]]/, '\\\\\&')}]" \\\n] } to << " '*:file:_files' && return 0\n" end From f7a407cabda6eb787fb95fc6e3c1b2215b1aec19 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Sat, 14 Oct 2023 21:52:01 +0300 Subject: [PATCH 092/142] [ruby/optparse] Fix `require_exact` to work with options defined as `--[no]-something` https://github.com/ruby/optparse/commit/4e346ad337 --- lib/optparse.rb | 6 +++--- test/optparse/test_optparse.rb | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ecf6adc45fc5f4..ccfea6dcf0f668 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1048,7 +1048,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: # Shows option summary. # Officious['help'] = proc do |parser| - Switch::NoArgument.new do |arg| + Switch::NoArgument.new(nil, nil, ["-h"], ["--help"]) do |arg| puts parser.help exit end @@ -1473,7 +1473,7 @@ def make_switch(opts, block = nil) default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end - ldesc << "--[no-]#{q}" + ldesc << "--#{q}" << "--no-#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style @@ -1649,7 +1649,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt.tr!('_', '-') begin sw, = complete(:long, opt, true) - if require_exact && !sw.long.include?(arg) + if require_exact && !sw.long.include?("--#{opt}") throw :terminate, arg unless raise_unknown raise InvalidOption, arg end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index bfa705ad03ab4d..be9bcb8425b26e 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -88,9 +88,9 @@ def test_require_exact end @opt.require_exact = true - %w(--zrs -F -Ffoo).each do |arg| + [%w(--zrs foo), %w(--zrs=foo), %w(-F foo), %w(-Ffoo)].each do |args| result = {} - @opt.parse([arg, 'foo'], into: result) + @opt.parse(args, into: result) assert_equal({zrs: 'foo'}, result) end @@ -99,6 +99,14 @@ def test_require_exact assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zrs foo))} assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zr foo))} assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-z foo))} + + @opt.def_option('-f', '--[no-]foo', 'foo') {|arg| @foo = arg} + @opt.parse(%w[-f]) + assert_equal(true, @foo) + @opt.parse(%w[--foo]) + assert_equal(true, @foo) + @opt.parse(%w[--no-foo]) + assert_equal(false, @foo) end def test_raise_unknown From 33c1e082d0807db403a2d93cbf0a094c91179d74 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 3 Feb 2024 00:50:02 +0900 Subject: [PATCH 093/142] Remove ruby object from string nodes String nodes holds ruby string object on `VALUE nd_lit`. This commit changes it to `struct rb_parser_string *string` to reduce dependency on ruby object. Sometimes these strings are concatenated with other string therefore string concatenate functions are needed. --- ast.c | 5 +- compile.c | 25 +- internal/ruby_parser.h | 3 + node.c | 38 +-- node_dump.c | 11 +- parse.y | 634 ++++++++++++++++++++++++++++++++--------- ruby_parser.c | 28 ++ rubyparser.h | 27 +- universal_parser.c | 1 + 9 files changed, 595 insertions(+), 177 deletions(-) diff --git a/ast.c b/ast.c index c938e3e208e628..710b2136a27b01 100644 --- a/ast.c +++ b/ast.c @@ -555,9 +555,10 @@ node_children(rb_ast_t *ast, const NODE *node) return rb_ary_new_from_node_args(ast, 2, RNODE_MATCH3(node)->nd_recv, RNODE_MATCH3(node)->nd_value); case NODE_MATCH: case NODE_LIT: + return rb_ary_new_from_args(1, RNODE_LIT(node)->nd_lit); case NODE_STR: case NODE_XSTR: - return rb_ary_new_from_args(1, RNODE_LIT(node)->nd_lit); + return rb_ary_new_from_args(1, rb_node_str_string_val(node)); case NODE_INTEGER: return rb_ary_new_from_args(1, rb_node_integer_literal_val(node)); case NODE_FLOAT: @@ -579,7 +580,7 @@ node_children(rb_ast_t *ast, const NODE *node) head = NEW_CHILD(ast, n->nd_head); next = NEW_CHILD(ast, n->nd_next); } - return rb_ary_new_from_args(3, RNODE_DSTR(node)->nd_lit, head, next); + return rb_ary_new_from_args(3, rb_node_dstr_string_val(node), head, next); } case NODE_SYM: return rb_ary_new_from_args(1, rb_node_sym_string_val(node)); diff --git a/compile.c b/compile.c index 6f012b59df457a..8ced4d8cc190d5 100644 --- a/compile.c +++ b/compile.c @@ -838,7 +838,7 @@ get_string_value(const NODE *node) { switch (nd_type(node)) { case NODE_STR: - return RNODE_STR(node)->nd_lit; + return rb_node_str_string_val(node); case NODE_FILE: return rb_node_file_path_val(node); default: @@ -4310,7 +4310,7 @@ static int compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int *cntp) { const struct RNode_LIST *list = RNODE_DSTR(node)->nd_next; - VALUE lit = RNODE_DSTR(node)->nd_lit; + VALUE lit = rb_node_dstr_string_val(node); LINK_ELEMENT *first_lit = 0; int cnt = 0; @@ -4331,7 +4331,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(RNODE_STR(head)->nd_lit); + lit = rb_fstring(rb_node_str_string_val(head)); ADD_INSN1(ret, head, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); lit = Qnil; @@ -4370,7 +4370,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(RNODE_DSTR(node)->nd_lit); + VALUE lit = rb_fstring(rb_node_dstr_string_val(node)); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -4387,14 +4387,13 @@ compile_dregx(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i int cnt; if (!RNODE_DREGX(node)->nd_next) { - VALUE match = RNODE_DREGX(node)->nd_lit; - if (RB_TYPE_P(match, T_REGEXP)) { - if (!popped) { - ADD_INSN1(ret, node, putobject, match); - RB_OBJ_WRITTEN(iseq, Qundef, match); - } - return COMPILE_OK; + if (!popped) { + VALUE src = rb_node_dregx_string_val(node); + VALUE match = rb_reg_compile(src, (int)RNODE_DREGX(node)->nd_cflag, NULL, 0); + ADD_INSN1(ret, node, putobject, match); + RB_OBJ_WRITTEN(iseq, Qundef, match); } + return COMPILE_OK; } CHECK(compile_dstr_fragments(iseq, ret, node, &cnt)); @@ -5135,7 +5134,7 @@ 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(RNODE_STR(node)->nd_lit); + return rb_fstring(rb_node_str_string_val(node)); case NODE_FILE: return rb_fstring(rb_node_file_path_val(node)); } @@ -10364,7 +10363,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(RNODE_XSTR(node)->nd_lit); + VALUE str = rb_fstring(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 ed0165f891819b..a93a12ae999124 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -76,7 +76,10 @@ RUBY_SYMBOL_EXPORT_BEGIN VALUE rb_str_new_parser_string(rb_parser_string_t *str); RUBY_SYMBOL_EXPORT_END +VALUE rb_node_str_string_val(const NODE *); VALUE rb_node_sym_string_val(const NODE *); +VALUE rb_node_dstr_string_val(const NODE *); +VALUE rb_node_dregx_string_val(const NODE *); VALUE rb_node_line_lineno_val(const NODE *); VALUE rb_node_file_path_val(const NODE *); VALUE rb_node_encoding_val(const NODE *); diff --git a/node.c b/node.c index 9844401331977d..9123b606d921c5 100644 --- a/node.c +++ b/node.c @@ -172,6 +172,8 @@ struct rb_ast_local_table_link { static void parser_string_free(rb_ast_t *ast, rb_parser_string_t *str) { + if (!str) return; + xfree(str->ptr); xfree(str); } @@ -179,9 +181,27 @@ static void free_ast_value(rb_ast_t *ast, void *ctx, NODE *node) { switch (nd_type(node)) { + case NODE_STR: + parser_string_free(ast, RNODE_STR(node)->string); + break; + case NODE_DSTR: + parser_string_free(ast, RNODE_DSTR(node)->string); + break; + case NODE_XSTR: + parser_string_free(ast, RNODE_XSTR(node)->string); + break; + case NODE_DXSTR: + parser_string_free(ast, RNODE_DXSTR(node)->string); + break; case NODE_SYM: parser_string_free(ast, RNODE_SYM(node)->string); break; + case NODE_DSYM: + parser_string_free(ast, RNODE_DSYM(node)->string); + break; + case NODE_DREGX: + parser_string_free(ast, RNODE_DREGX(node)->string); + break; case NODE_FILE: parser_string_free(ast, RNODE_FILE(node)->path); break; @@ -251,12 +271,6 @@ nodetype_markable_p(enum node_type type) switch (type) { case NODE_MATCH: case NODE_LIT: - case NODE_STR: - case NODE_XSTR: - case NODE_DSTR: - case NODE_DXSTR: - case NODE_DREGX: - case NODE_DSYM: return true; default: return false; @@ -363,12 +377,6 @@ mark_ast_value(rb_ast_t *ast, void *ctx, NODE *node) switch (nd_type(node)) { case NODE_MATCH: case NODE_LIT: - case NODE_STR: - case NODE_XSTR: - case NODE_DSTR: - case NODE_DXSTR: - case NODE_DREGX: - case NODE_DSYM: rb_gc_mark_movable(RNODE_LIT(node)->nd_lit); break; default: @@ -386,12 +394,6 @@ update_ast_value(rb_ast_t *ast, void *ctx, NODE *node) switch (nd_type(node)) { case NODE_MATCH: case NODE_LIT: - case NODE_STR: - case NODE_XSTR: - case NODE_DSTR: - case NODE_DXSTR: - case NODE_DREGX: - case NODE_DSYM: RNODE_LIT(node)->nd_lit = rb_gc_location(RNODE_LIT(node)->nd_lit); break; default: diff --git a/node_dump.c b/node_dump.c index 5670279d975390..5bf95911153d17 100644 --- a/node_dump.c +++ b/node_dump.c @@ -707,18 +707,19 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("literal"); ANN("format: [nd_lit]"); ANN("example: :sym, /foo/"); - goto lit; + F_LIT(nd_lit, RNODE_LIT, "literal"); + return; case NODE_STR: ANN("string literal"); ANN("format: [nd_lit]"); ANN("example: 'foo'"); - goto lit; + goto str; case NODE_XSTR: ANN("xstring literal"); ANN("format: [nd_lit]"); ANN("example: `foo`"); - lit: - F_LIT(nd_lit, RNODE_LIT, "literal"); + str: + F_VALUE(string, rb_node_str_string_val(node), "literal"); return; case NODE_INTEGER: @@ -777,7 +778,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: [nd_lit]"); ANN("example: :\"foo#{ bar }baz\""); dlit: - F_LIT(nd_lit, RNODE_DSTR, "preceding string"); + F_VALUE(string, rb_node_dstr_string_val(node), "preceding string"); if (!RNODE_DSTR(node)->nd_next) return; F_NODE(nd_next->nd_head, RNODE_DSTR, "interpolation"); LAST_NODE; diff --git a/parse.y b/parse.y index f9d224df34a475..e14fb8eb699040 100644 --- a/parse.y +++ b/parse.y @@ -694,6 +694,8 @@ static void numparam_name(struct parser_params *p, ID id); #define intern_cstr(n,l,en) rb_intern3(n,l,en) +#define STRING_NEW0() rb_parser_encoding_string_new(p,0,0,p->enc) + #define STR_NEW(ptr,len) rb_enc_str_new((ptr),(len),p->enc) #define STR_NEW0() rb_enc_str_new(0,0,p->enc) #define STR_NEW2(ptr) rb_enc_str_new((ptr),strlen(ptr),p->enc) @@ -1122,11 +1124,11 @@ static rb_node_integer_t * rb_node_integer_new(struct parser_params *p, char* va static rb_node_float_t * rb_node_float_new(struct parser_params *p, char* val, const YYLTYPE *loc); static rb_node_rational_t * rb_node_rational_new(struct parser_params *p, char* val, int base, int seen_point, const YYLTYPE *loc); static rb_node_imaginary_t * rb_node_imaginary_new(struct parser_params *p, char* val, int base, int seen_point, enum rb_numeric_type, const YYLTYPE *loc); -static rb_node_str_t *rb_node_str_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc); -static rb_node_dstr_t *rb_node_dstr_new0(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc); -static rb_node_dstr_t *rb_node_dstr_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc); -static rb_node_xstr_t *rb_node_xstr_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc); -static rb_node_dxstr_t *rb_node_dxstr_new(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc); +static rb_node_str_t *rb_node_str_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc); +static rb_node_dstr_t *rb_node_dstr_new0(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc); +static rb_node_dstr_t *rb_node_dstr_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc); +static rb_node_xstr_t *rb_node_xstr_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc); +static rb_node_dxstr_t *rb_node_dxstr_new(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc); static rb_node_evstr_t *rb_node_evstr_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_once_t *rb_node_once_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_args_t *rb_node_args_new(struct parser_params *p, const YYLTYPE *loc); @@ -1158,7 +1160,7 @@ static rb_node_errinfo_t *rb_node_errinfo_new(struct parser_params *p, const YYL static rb_node_defined_t *rb_node_defined_new(struct parser_params *p, NODE *nd_head, const YYLTYPE *loc); static rb_node_postexe_t *rb_node_postexe_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_sym_t *rb_node_sym_new(struct parser_params *p, VALUE str, const YYLTYPE *loc); -static rb_node_dsym_t *rb_node_dsym_new(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc); +static rb_node_dsym_t *rb_node_dsym_new(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc); static rb_node_attrasgn_t *rb_node_attrasgn_new(struct parser_params *p, NODE *nd_recv, ID nd_mid, NODE *nd_args, const YYLTYPE *loc); static rb_node_lambda_t *rb_node_lambda_new(struct parser_params *p, rb_node_args_t *nd_args, NODE *nd_body, const YYLTYPE *loc); static rb_node_aryptn_t *rb_node_aryptn_new(struct parser_params *p, NODE *pre_args, NODE *rest_arg, NODE *post_args, const YYLTYPE *loc); @@ -1492,9 +1494,9 @@ static rb_ast_id_table_t *local_tbl(struct parser_params*); static VALUE reg_compile(struct parser_params*, VALUE, int); static void reg_fragment_setenc(struct parser_params*, VALUE, int); -static int reg_fragment_check(struct parser_params*, VALUE, int); +static int reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); -static int literal_concat0(struct parser_params *p, VALUE head, VALUE tail); +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); @@ -2130,25 +2132,40 @@ get_nd_args(struct parser_params *p, NODE *node) } #endif +#define PARSER_STRING_PTR(str) (str->ptr) +#define PARSER_STRING_LEN(str) (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') +#define PARSER_STRING_RESIZE_CAPA_TERM(p,str,capacity,termlen) do {\ + SIZED_REALLOC_N(str->ptr, char, (size_t)total + termlen, STRING_SIZE(str)); \ + str->len = total; \ +} while (0) +#define STRING_SET_LEN(str, n) do { \ + (str)->len = (n); \ +} while (0) +#define PARSER_STRING_GETMEM(str, ptrvar, lenvar) \ + ((ptrvar) = str->ptr, \ + (lenvar) = str->len) + #ifndef RIPPER static rb_parser_string_t * rb_parser_string_new(rb_parser_t *p, const char *ptr, long len) { - size_t size; rb_parser_string_t *str; if (len < 0) { rb_bug("negative string size (or size too big): %ld", len); } - size = offsetof(rb_parser_string_t, ptr) + len + 1; - str = xcalloc(1, size); + str = xcalloc(1, sizeof(rb_parser_string_t)); + str->ptr = xcalloc(len + 1, sizeof(char)); if (ptr) { - memcpy(str->ptr, ptr, len); + memcpy(PARSER_STRING_PTR(str), ptr, len); } - str->len = len; - str->ptr[len] = '\0'; + STRING_SET_LEN(str, len); + STRING_TERM_FILL(str); return str; } @@ -2156,6 +2173,7 @@ static rb_parser_string_t * rb_parser_encoding_string_new(rb_parser_t *p, const char *ptr, long len, rb_encoding *enc) { rb_parser_string_t *str = rb_parser_string_new(p, ptr, len); + str->coderange = RB_PARSER_ENC_CODERANGE_UNKNOWN; str->enc = enc; return str; } @@ -2164,10 +2182,18 @@ rb_parser_encoding_string_new(rb_parser_t *p, const char *ptr, long len, rb_enco static void rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *str) { + if (!str) return; + xfree(PARSER_STRING_PTR(str)); xfree(str); } #ifndef RIPPER +static size_t +rb_parser_str_capacity(rb_parser_string_t *str, const int termlen) +{ + return PARSER_STRING_LEN(str); +} + static char * rb_parser_string_end(rb_parser_string_t *str) { @@ -2179,29 +2205,354 @@ rb_parser_string_set_encoding(rb_parser_string_t *str, rb_encoding *enc) { str->enc = enc; } +#endif -long -rb_parser_string_length(rb_parser_string_t *str) +static rb_encoding * +rb_parser_str_get_encoding(rb_parser_string_t *str) { - return str->len; + return str->enc; } -char * -rb_parser_string_pointer(rb_parser_string_t *str) +#ifndef RIPPER +static int +PARSER_ENC_CODERANGE(rb_parser_string_t *str) { - return str->ptr; + return str->coderange; +} + +static void +PARSER_ENC_CODERANGE_SET(rb_parser_string_t *str, int coderange) +{ + str->coderange = coderange; +} + +static void +PARSER_ENCODING_CODERANGE_SET(rb_parser_string_t *str, rb_encoding *enc, enum rb_parser_string_coderange_type cr) +{ + rb_parser_string_set_encoding(str, enc); + PARSER_ENC_CODERANGE_SET(str, cr); +} + +static void +PARSER_ENCODING_CODERANGE_CLEAR(rb_parser_string_t *str) +{ + str->coderange = RB_PARSER_ENC_CODERANGE_UNKNOWN; +} + +static bool +PARSER_ENC_CODERANGE_CLEAN_P(int cr) +{ + return cr == RB_PARSER_ENC_CODERANGE_7BIT || cr == RB_PARSER_ENC_CODERANGE_VALID; +} + +static const char * +rb_parser_search_nonascii(const char *p, const char *e) +{ + const char *s = p; + + for (; s < e; s++) { + if (*s & 0x80) return s; + } + + return NULL; +} + +static int +rb_parser_coderange_scan(struct parser_params *p, const char *ptr, long len, rb_encoding *enc) +{ + const char *e = ptr + len; + + if (enc == rb_ascii8bit_encoding()) { + /* enc is ASCII-8BIT. ASCII-8BIT string never be broken. */ + ptr = rb_parser_search_nonascii(ptr, e); + return p ? RB_PARSER_ENC_CODERANGE_VALID : RB_PARSER_ENC_CODERANGE_7BIT; + } + + /* parser string encoding is always asciicompat */ + ptr = rb_parser_search_nonascii(ptr, e); + if (!ptr) return RB_PARSER_ENC_CODERANGE_7BIT; + for (;;) { + int ret = rb_enc_precise_mbclen(ptr, e, enc); + if (!MBCLEN_CHARFOUND_P(ret)) return RB_PARSER_ENC_CODERANGE_BROKEN; + ptr += MBCLEN_CHARFOUND_LEN(ret); + if (ptr == e) break; + ptr = rb_parser_search_nonascii(ptr, e); + if (!ptr) break; + } + + return RB_PARSER_ENC_CODERANGE_VALID; +} + +static int +rb_parser_enc_coderange_scan(struct parser_params *p, rb_parser_string_t *str, rb_encoding *enc) +{ + return rb_parser_coderange_scan(p, PARSER_STRING_PTR(str), PARSER_STRING_LEN(str), enc); +} + +static int +rb_parser_enc_str_coderange(struct parser_params *p, rb_parser_string_t *str) +{ + int cr = PARSER_ENC_CODERANGE(str); + + if (cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + cr = rb_parser_enc_coderange_scan(p, str, rb_parser_str_get_encoding(str)); + PARSER_ENC_CODERANGE_SET(str, cr); + } + + return cr; +} + +static bool +rb_parser_is_ascii_string(struct parser_params *p, rb_parser_string_t *str) +{ + return rb_parser_enc_str_coderange(p, str) == RB_PARSER_ENC_CODERANGE_7BIT; +} + +static int +rb_parser_enc_str_asciionly_p(struct parser_params *p, rb_parser_string_t *str) +{ + rb_encoding *enc = rb_parser_str_get_encoding(str); + + if (!rb_enc_asciicompat(enc)) + return FALSE; + else if (rb_parser_is_ascii_string(p, str)) + return TRUE; + return FALSE; } -#endif static rb_encoding * -rb_parser_string_encoding(rb_parser_string_t *str) +rb_parser_enc_compatible_latter(struct parser_params *p, rb_parser_string_t *str1, rb_parser_string_t *str2, rb_encoding *enc1, rb_encoding *enc2) { - return str->enc; + int cr1, cr2; + + if (PARSER_STRING_LEN(str2) == 0) + return enc1; + if (PARSER_STRING_LEN(str1) == 0) + return (rb_enc_asciicompat(enc1) && rb_parser_enc_str_asciionly_p(p, str2)) ? enc1 : enc2; + if (!rb_enc_asciicompat(enc1) || !rb_enc_asciicompat(enc2)) { + return 0; + } + + cr1 = rb_parser_enc_str_coderange(p, str1); + cr2 = rb_parser_enc_str_coderange(p, str2); + + if (cr1 != cr2) { + if (cr1 == RB_PARSER_ENC_CODERANGE_7BIT) return enc2; + if (cr2 == RB_PARSER_ENC_CODERANGE_7BIT) return enc1; + } + + if (cr2 == RB_PARSER_ENC_CODERANGE_7BIT) { + return enc1; + } + + if (cr1 == RB_PARSER_ENC_CODERANGE_7BIT) { + return enc2; + } + + return 0; +} + +static rb_encoding * +rb_parser_enc_compatible(struct parser_params *p, rb_parser_string_t *str1, rb_parser_string_t *str2) +{ + rb_encoding *enc1 = rb_parser_str_get_encoding(str1); + rb_encoding *enc2 = rb_parser_str_get_encoding(str2); + + if (enc1 == NULL || enc2 == NULL) + return 0; + + if (enc1 == enc2) { + return enc1; + } + + return rb_parser_enc_compatible_latter(p, str1, str2, enc1, enc2); +} + +static void +rb_parser_str_modify(rb_parser_string_t *str) +{ + PARSER_ENCODING_CODERANGE_CLEAR(str); +} + +static void +rb_parser_str_set_len(struct parser_params *p, rb_parser_string_t *str, long len) +{ + long capa; + const int termlen = STRING_TERM_LEN(str); + + if (len > (capa = (long)(rb_parser_str_capacity(str, termlen))) || len < 0) { + rb_bug("probable buffer overflow: %ld for %ld", len, capa); + } + + int cr = PARSER_ENC_CODERANGE(str); + if (cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + /* Leave unknown. */ + } + else if (len > PARSER_STRING_LEN(str)) { + PARSER_ENC_CODERANGE_SET(str, RB_PARSER_ENC_CODERANGE_UNKNOWN); + } + else if (len < PARSER_STRING_LEN(str)) { + if (cr != RB_PARSER_ENC_CODERANGE_7BIT) { + /* ASCII-only string is keeping after truncated. Valid + * and broken may be invalid or valid, leave unknown. */ + PARSER_ENC_CODERANGE_SET(str, RB_PARSER_ENC_CODERANGE_UNKNOWN); + } + } + + STRING_SET_LEN(str, len); + STRING_TERM_FILL(str); +} + +static rb_parser_string_t * +rb_parser_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, const char *ptr, long len) +{ + rb_parser_str_modify(str); + if (len == 0) return 0; + + long total, olen, off = -1; + char *sptr; + const int termlen = STRING_TERM_LEN(str); + + PARSER_STRING_GETMEM(str, sptr, olen); + if (ptr >= sptr && ptr <= sptr + olen) { + off = ptr - sptr; + } + + if (olen > LONG_MAX - len) { + compile_error(p, "string sizes too big"); + return 0; + } + total = olen + len; + PARSER_STRING_RESIZE_CAPA_TERM(p, str, total, termlen); + sptr = PARSER_STRING_PTR(str); + if (off != -1) { + ptr = sptr + off; + } + memcpy(sptr + olen, ptr, len); + STRING_SET_LEN(str, total); + STRING_TERM_FILL(str); + + return str; +} + +static rb_parser_string_t * +rb_parser_enc_cr_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, const char *ptr, long len, + rb_encoding *ptr_enc, int ptr_cr, int *ptr_cr_ret) +{ + int str_cr, res_cr; + rb_encoding *str_enc, *res_enc; + + str_enc = rb_parser_str_get_encoding(str); + str_cr = PARSER_STRING_LEN(str) ? PARSER_ENC_CODERANGE(str) : RB_PARSER_ENC_CODERANGE_7BIT; + + if (str_enc == ptr_enc) { + if (str_cr != RB_PARSER_ENC_CODERANGE_UNKNOWN && ptr_cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + ptr_cr = rb_parser_coderange_scan(p, ptr, len, ptr_enc); + } + } + else { + /* parser string encoding is always asciicompat */ + if (ptr_cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + ptr_cr = rb_parser_coderange_scan(p, ptr, len, ptr_enc); + } + if (str_cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + if (str_enc == rb_ascii8bit_encoding() || ptr_cr != RB_PARSER_ENC_CODERANGE_7BIT) { + str_cr = rb_parser_enc_str_coderange(p, str); + } + } + } + if (ptr_cr_ret) + *ptr_cr_ret = ptr_cr; + + if (str_enc != ptr_enc && + str_cr != RB_PARSER_ENC_CODERANGE_7BIT && + ptr_cr != RB_PARSER_ENC_CODERANGE_7BIT) { + goto incompatible; + } + + if (str_cr == RB_PARSER_ENC_CODERANGE_UNKNOWN) { + res_enc = str_enc; + res_cr = RB_PARSER_ENC_CODERANGE_UNKNOWN; + } + else if (str_cr == RB_PARSER_ENC_CODERANGE_7BIT) { + if (ptr_cr == RB_PARSER_ENC_CODERANGE_7BIT) { + res_enc = str_enc; + res_cr = RB_PARSER_ENC_CODERANGE_7BIT; + } + else { + res_enc = ptr_enc; + res_cr = ptr_cr; + } + } + else if (str_cr == RB_PARSER_ENC_CODERANGE_VALID) { + res_enc = str_enc; + if (PARSER_ENC_CODERANGE_CLEAN_P(ptr_cr)) + res_cr = str_cr; + else + res_cr = ptr_cr; + } + else { /* str_cr == RB_PARSER_ENC_CODERANGE_BROKEN */ + res_enc = str_enc; + res_cr = str_cr; + if (0 < len) res_cr = RB_PARSER_ENC_CODERANGE_UNKNOWN; + } + + if (len < 0) { + compile_error(p, "negative string size (or size too big)"); + } + rb_parser_str_buf_cat(p, str, ptr, len); + PARSER_ENCODING_CODERANGE_SET(str, res_enc, res_cr); + return str; + + incompatible: + compile_error(p, "incompatible character encodings: %s and %s", + rb_enc_name(str_enc), rb_enc_name(ptr_enc)); + UNREACHABLE_RETURN(0); + +} + +static rb_parser_string_t * +rb_parser_str_buf_append(struct parser_params *p, rb_parser_string_t *str, rb_parser_string_t *str2) +{ + int str2_cr = rb_parser_enc_str_coderange(p, str2); + + rb_parser_enc_cr_str_buf_cat(p, str, PARSER_STRING_PTR(str2), PARSER_STRING_LEN(str2), + rb_parser_str_get_encoding(str2), str2_cr, &str2_cr); + + PARSER_ENC_CODERANGE_SET(str2, str2_cr); + + return str; +} + +static rb_parser_string_t * +rb_parser_str_resize(struct parser_params *p, rb_parser_string_t *str, long len) +{ + if (len < 0) { + rb_bug("negative string size (or size too big)"); + } + + long slen = PARSER_STRING_LEN(str); + + if (slen > len && PARSER_ENC_CODERANGE(str) != RB_PARSER_ENC_CODERANGE_7BIT) { + PARSER_ENCODING_CODERANGE_CLEAR(str); + } + + { + long capa; + const int termlen = STRING_TERM_LEN(str); + + if ((capa = slen) < len) { + SIZED_REALLOC_N(str->ptr, char, (size_t)len + termlen, STRING_SIZE(str)); + } + else if (len == slen) return str; + STRING_SET_LEN(str, len); + STRING_TERM_FILL(str); + } + return str; } -#ifndef RIPPER #ifndef UNIVERSAL_PARSER -# define PARSER_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ +# define PARSER_ENC_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ ((ptrvar) = str->ptr, \ (lenvar) = str->len, \ (encvar) = str->enc) @@ -2213,8 +2564,8 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) const char *ptr1, *ptr2; rb_encoding *enc1, *enc2; - PARSER_STRING_GETMEM(str1, ptr1, len1, enc1); - PARSER_STRING_GETMEM(str2, ptr2, len2, enc2); + PARSER_ENC_STRING_GETMEM(str1, ptr1, len1, enc1); + PARSER_ENC_STRING_GETMEM(str2, ptr2, len2, enc2); return (len1 != len2 || enc1 != enc2 || @@ -6001,8 +6352,7 @@ strings : string /*%%%*/ NODE *node = $1; if (!node) { - node = NEW_STR(STR_NEW0(), &@$); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_STR(node)->nd_lit); + node = NEW_STR(STRING_NEW0(), &@$); } else { node = evstr2dstr(p, node); @@ -6238,7 +6588,7 @@ regexp_contents: /* none */ case NODE_DSTR: break; default: - head = list_append(p, NEW_DSTR(Qnil, &@$), head); + head = list_append(p, NEW_DSTR(0, &@$), head); break; } $$ = list_append(p, head, tail); @@ -7140,8 +7490,7 @@ static enum yytokentype here_document(struct parser_params*,rb_strterm_heredoc_t } # define set_yylval_str(x) \ do { \ - set_yylval_node(NEW_STR(x, &_cur_loc)); \ - RB_OBJ_WRITTEN(p->ast, Qnil, x); \ + set_yylval_node(NEW_STR(rb_str_to_parser_string(p, x), &_cur_loc)); \ } while(0) # define set_yylval_num(x) (yylval.num = (x)) # define set_yylval_id(x) (yylval.id = (x)) @@ -7460,7 +7809,7 @@ ruby_show_error_line(struct parser_params *p, VALUE errbuf, const YYLTYPE *yyllo const char *pre = "", *post = "", *pend; const char *code = "", *caret = ""; const char *lim; - const char *const pbeg = rb_parser_string_pointer(str); + const char *const pbeg = PARSER_STRING_PTR(str); char *buf; long len; int i; @@ -7487,11 +7836,11 @@ ruby_show_error_line(struct parser_params *p, VALUE errbuf, const YYLTYPE *yyllo len = ptr_end - ptr; if (len > 4) { if (ptr > pbeg) { - ptr = rb_enc_prev_char(pbeg, ptr, pt, rb_parser_string_encoding(str)); + ptr = rb_enc_prev_char(pbeg, ptr, pt, rb_parser_str_get_encoding(str)); if (ptr > pbeg) pre = "..."; } if (ptr_end < pend) { - ptr_end = rb_enc_prev_char(pt, ptr_end, pend, rb_parser_string_encoding(str)); + ptr_end = rb_enc_prev_char(pt, ptr_end, pend, rb_parser_str_get_encoding(str)); if (ptr_end < pend) post = "..."; } } @@ -7510,7 +7859,7 @@ ruby_show_error_line(struct parser_params *p, VALUE errbuf, const YYLTYPE *yyllo rb_str_cat_cstr(mesg, "\n"); } else { - mesg = rb_enc_str_new(0, 0, rb_parser_string_encoding(str)); + mesg = rb_enc_str_new(0, 0, rb_parser_str_get_encoding(str)); } if (!errbuf && rb_stderr_tty_p()) { #define CSI_BEGIN "\033[" @@ -8004,8 +8353,8 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int static void set_lastline(struct parser_params *p, rb_parser_string_t *str) { - p->lex.pbeg = p->lex.pcur = rb_parser_string_pointer(str); - p->lex.pend = p->lex.pcur + rb_parser_string_length(str); + p->lex.pbeg = p->lex.pcur = PARSER_STRING_PTR(str); + p->lex.pend = p->lex.pcur + PARSER_STRING_LEN(str); p->lex.lastline = str; } @@ -9104,8 +9453,8 @@ heredoc_restore(struct parser_params *p, rb_strterm_heredoc_t *here) p->lex.strterm = 0; line = here->lastline; p->lex.lastline = line; - p->lex.pbeg = rb_parser_string_pointer(line); - p->lex.pend = p->lex.pbeg + rb_parser_string_length(line); + p->lex.pbeg = PARSER_STRING_PTR(line); + p->lex.pend = p->lex.pbeg + PARSER_STRING_LEN(line); p->lex.pcur = p->lex.pbeg + here->offset + here->length + here->quote; p->lex.ptok = p->lex.pbeg + here->offset - here->quote; p->heredoc_end = p->ruby_sourceline; @@ -9116,13 +9465,10 @@ heredoc_restore(struct parser_params *p, rb_strterm_heredoc_t *here) } static int -dedent_string(struct parser_params *p, VALUE string, int width) +dedent_string_column(const char *str, long len, int width) { - char *str; - long len; int i, col = 0; - RSTRING_GETMEM(string, str, len); for (i = 0; i < len && col < width; i++) { if (str[i] == ' ') { col++; @@ -9136,23 +9482,39 @@ dedent_string(struct parser_params *p, VALUE string, int width) break; } } + + return i; +} + +#ifndef RIPPER +static int +dedent_string(struct parser_params *p, rb_parser_string_t *string, int width) +{ + char *str; + long len; + int i; + + len = PARSER_STRING_LEN(string); + str = PARSER_STRING_PTR(string); + + i = dedent_string_column(str, len, width); if (!i) return 0; - rb_str_modify(string); - str = RSTRING_PTR(string); - if (RSTRING_LEN(string) != len) - rb_fatal("literal string changed: %+"PRIsVALUE, string); + + rb_parser_str_modify(string); + str = PARSER_STRING_PTR(string); + if (PARSER_STRING_LEN(string) != len) + rb_fatal("literal string changed: %s", PARSER_STRING_PTR(string)); MEMMOVE(str, str + i, char, len - i); - rb_str_set_len(string, len - i); + rb_parser_str_set_len(p, string, len - i); return i; } -#ifndef RIPPER static NODE * heredoc_dedent(struct parser_params *p, NODE *root) { NODE *node, *str_node, *prev_node; int indent = p->heredoc_indent; - VALUE prev_lit = 0; + rb_parser_string_t *prev_lit = 0; if (indent <= 0) return root; p->heredoc_indent = 0; @@ -9162,7 +9524,7 @@ heredoc_dedent(struct parser_params *p, NODE *root) if (nd_type_p(root, NODE_LIST)) str_node = RNODE_LIST(root)->nd_head; while (str_node) { - VALUE lit = RNODE_LIT(str_node)->nd_lit; + rb_parser_string_t *lit = RNODE_STR(str_node)->string; if (nd_fl_newline(str_node)) { dedent_string(p, lit, indent); } @@ -9359,7 +9721,7 @@ here_document(struct parser_params *p, rb_strterm_heredoc_t *here) rb_encoding *base_enc = 0; int bol; - eos = rb_parser_string_pointer(here->lastline) + here->offset; + eos = PARSER_STRING_PTR(here->lastline) + here->offset; len = here->length; indent = (func = here->func) & STR_FUNC_INDENT; @@ -9415,7 +9777,7 @@ here_document(struct parser_params *p, rb_strterm_heredoc_t *here) if (!(func & STR_FUNC_EXPAND)) { do { - ptr = rb_parser_string_pointer(p->lex.lastline); + ptr = PARSER_STRING_PTR(p->lex.lastline); ptr_end = p->lex.pend; if (ptr_end > ptr) { switch (ptr_end[-1]) { @@ -9979,7 +10341,7 @@ parser_prepare(struct parser_params *p) return; } pushback(p, c); - p->enc = rb_parser_string_encoding(p->lex.lastline); + p->enc = rb_parser_str_get_encoding(p->lex.lastline); } #ifndef RIPPER @@ -12252,20 +12614,20 @@ rb_node_imaginary_new(struct parser_params *p, char* val, int base, int seen_poi } static rb_node_str_t * -rb_node_str_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc) +rb_node_str_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc) { rb_node_str_t *n = NODE_NEWNODE(NODE_STR, rb_node_str_t, loc); - n->nd_lit = nd_lit; + n->string = string; return n; } /* TODO; Use union for NODE_DSTR2 */ static rb_node_dstr_t * -rb_node_dstr_new0(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc) +rb_node_dstr_new0(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc) { rb_node_dstr_t *n = NODE_NEWNODE(NODE_DSTR, rb_node_dstr_t, loc); - n->nd_lit = nd_lit; + n->string = string; n->as.nd_alen = nd_alen; n->nd_next = (rb_node_list_t *)nd_next; @@ -12273,25 +12635,25 @@ rb_node_dstr_new0(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_ } static rb_node_dstr_t * -rb_node_dstr_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc) +rb_node_dstr_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc) { - return rb_node_dstr_new0(p, nd_lit, 1, 0, loc); + return rb_node_dstr_new0(p, string, 1, 0, loc); } static rb_node_xstr_t * -rb_node_xstr_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc) +rb_node_xstr_new(struct parser_params *p, rb_parser_string_t *string, const YYLTYPE *loc) { rb_node_xstr_t *n = NODE_NEWNODE(NODE_XSTR, rb_node_xstr_t, loc); - n->nd_lit = nd_lit; + n->string = string; return n; } static rb_node_dxstr_t * -rb_node_dxstr_new(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc) +rb_node_dxstr_new(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc) { rb_node_dxstr_t *n = NODE_NEWNODE(NODE_DXSTR, rb_node_dxstr_t, loc); - n->nd_lit = nd_lit; + n->string = string; n->nd_alen = nd_alen; n->nd_next = (rb_node_list_t *)nd_next; @@ -12308,10 +12670,10 @@ rb_node_sym_new(struct parser_params *p, VALUE str, const YYLTYPE *loc) } static rb_node_dsym_t * -rb_node_dsym_new(struct parser_params *p, VALUE nd_lit, long nd_alen, NODE *nd_next, const YYLTYPE *loc) +rb_node_dsym_new(struct parser_params *p, rb_parser_string_t *string, long nd_alen, NODE *nd_next, const YYLTYPE *loc) { rb_node_dsym_t *n = NODE_NEWNODE(NODE_DSYM, rb_node_dsym_t, loc); - n->nd_lit = nd_lit; + n->string = string; n->nd_alen = nd_alen; n->nd_next = (rb_node_list_t *)nd_next; @@ -12854,31 +13216,31 @@ list_concat(NODE *head, NODE *tail) } static int -literal_concat0(struct parser_params *p, VALUE head, VALUE tail) +literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_string_t *tail) { - if (NIL_P(tail)) return 1; - if (!rb_enc_compatible(head, tail)) { + if (!tail) return 1; + if (!rb_parser_enc_compatible(p, head, tail)) { compile_error(p, "string literal encodings differ (%s / %s)", - rb_enc_name(rb_enc_get(head)), - rb_enc_name(rb_enc_get(tail))); - rb_str_resize(head, 0); - rb_str_resize(tail, 0); + rb_enc_name(rb_parser_str_get_encoding(head)), + rb_enc_name(rb_parser_str_get_encoding(tail))); + rb_parser_str_resize(p, head, 0); + rb_parser_str_resize(p, tail, 0); return 0; } - rb_str_buf_append(head, tail); + rb_parser_str_buf_append(p, head, tail); return 1; } -static VALUE +static rb_parser_string_t * string_literal_head(struct parser_params *p, enum node_type htype, NODE *head) { - if (htype != NODE_DSTR) return Qfalse; + if (htype != NODE_DSTR) return false; if (RNODE_DSTR(head)->nd_next) { head = RNODE_LIST(RNODE_LIST(RNODE_DSTR(head)->nd_next)->as.nd_end)->nd_head; - if (!head || !nd_type_p(head, NODE_STR)) return Qfalse; + if (!head || !nd_type_p(head, NODE_STR)) return false; } - const VALUE lit = RNODE_DSTR(head)->nd_lit; - ASSUME(lit != Qfalse); + rb_parser_string_t *lit = RNODE_DSTR(head)->string; + ASSUME(lit != false); return lit; } @@ -12887,7 +13249,7 @@ static NODE * literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *loc) { enum node_type htype; - VALUE lit; + rb_parser_string_t *lit; if (!head) return tail; if (!tail) return head; @@ -12909,14 +13271,14 @@ literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *l } switch (nd_type(tail)) { case NODE_STR: - if ((lit = string_literal_head(p, htype, head)) != Qfalse) { + if ((lit = string_literal_head(p, htype, head)) != false) { htype = NODE_STR; } else { - lit = RNODE_DSTR(head)->nd_lit; + lit = RNODE_DSTR(head)->string; } if (htype == NODE_STR) { - if (!literal_concat0(p, lit, RNODE_STR(tail)->nd_lit)) { + if (!literal_concat0(p, lit, RNODE_STR(tail)->string)) { error: rb_discard_node(p, head); rb_discard_node(p, tail); @@ -12931,13 +13293,15 @@ literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *l case NODE_DSTR: if (htype == NODE_STR) { - if (!literal_concat0(p, RNODE_STR(head)->nd_lit, RNODE_DSTR(tail)->nd_lit)) + if (!literal_concat0(p, RNODE_STR(head)->string, RNODE_DSTR(tail)->string)) goto error; - RNODE_DSTR(tail)->nd_lit = RNODE_STR(head)->nd_lit; + rb_parser_string_free(p, RNODE_DSTR(tail)->string); + RNODE_DSTR(tail)->string = RNODE_STR(head)->string; + RNODE_STR(head)->string = NULL; rb_discard_node(p, head); head = tail; } - else if (NIL_P(RNODE_DSTR(tail)->nd_lit)) { + else if (!RNODE_DSTR(tail)->string) { append: RNODE_DSTR(head)->as.nd_alen += RNODE_DSTR(tail)->as.nd_alen - 1; if (!RNODE_DSTR(head)->nd_next) { @@ -12949,14 +13313,16 @@ literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *l } rb_discard_node(p, tail); } - else if ((lit = string_literal_head(p, htype, head)) != Qfalse) { - if (!literal_concat0(p, lit, RNODE_DSTR(tail)->nd_lit)) + else if ((lit = string_literal_head(p, htype, head)) != false) { + if (!literal_concat0(p, lit, RNODE_DSTR(tail)->string)) goto error; - RNODE_DSTR(tail)->nd_lit = Qnil; + rb_parser_string_free(p, RNODE_DSTR(tail)->string); + RNODE_DSTR(tail)->string = 0; goto append; } else { - list_concat(head, NEW_LIST2(NEW_STR(RNODE_DSTR(tail)->nd_lit, loc), RNODE_DSTR(tail)->as.nd_alen, (NODE *)RNODE_DSTR(tail)->nd_next, loc)); + list_concat(head, NEW_LIST2(NEW_STR(RNODE_DSTR(tail)->string, loc), RNODE_DSTR(tail)->as.nd_alen, (NODE *)RNODE_DSTR(tail)->nd_next, loc)); + RNODE_DSTR(tail)->string = 0; } break; @@ -12985,10 +13351,10 @@ str2dstr(struct parser_params *p, NODE *node) { NODE *new_node = (NODE *)NODE_NEW_INTERNAL(NODE_DSTR, rb_node_dstr_t); nd_copy_flag(new_node, node); - RNODE_DSTR(new_node)->nd_lit = RNODE_STR(node)->nd_lit; + RNODE_DSTR(new_node)->string = RNODE_STR(node)->string; RNODE_DSTR(new_node)->as.nd_alen = 0; RNODE_DSTR(new_node)->nd_next = 0; - RNODE_STR(node)->nd_lit = 0; + RNODE_STR(node)->string = 0; return new_node; } @@ -13023,9 +13389,7 @@ new_evstr(struct parser_params *p, NODE *node, const YYLTYPE *loc) static NODE * new_dstr(struct parser_params *p, NODE *node, const YYLTYPE *loc) { - VALUE lit = STR_NEW0(); - NODE *dstr = NEW_DSTR(lit, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, lit); + NODE *dstr = NEW_DSTR(STRING_NEW0(), loc); return list_append(p, dstr, node); } @@ -13328,7 +13692,7 @@ symbol_append(struct parser_params *p, NODE *symbols, NODE *symbol) nd_set_type(symbol, NODE_DSYM); break; case NODE_STR: - symbol = NEW_SYM(RNODE_LIT(symbol)->nd_lit, &RNODE(symbol)->nd_loc); + symbol = NEW_SYM(rb_node_str_string_val(symbol), &RNODE(symbol)->nd_loc); break; default: compile_error(p, "unexpected node as symbol: %s", parser_node_name(type)); @@ -13341,7 +13705,6 @@ new_regexp(struct parser_params *p, NODE *node, int options, const YYLTYPE *loc) { struct RNode_LIST *list; NODE *prev; - VALUE lit; if (!node) { node = NEW_LIT(reg_compile(p, STR_NEW0(), options), loc); @@ -13351,33 +13714,30 @@ new_regexp(struct parser_params *p, NODE *node, int options, const YYLTYPE *loc) switch (nd_type(node)) { case NODE_STR: { - VALUE src = RNODE_STR(node)->nd_lit; - nd_set_type(node, NODE_LIT); - nd_set_loc(node, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(node)->nd_lit = reg_compile(p, src, options)); + VALUE src = rb_node_str_string_val(node); + node = NEW_LIT(reg_compile(p, src, options), loc); + RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(node)->nd_lit); } break; default: - lit = STR_NEW0(); - node = NEW_DSTR0(lit, 1, NEW_LIST(node, loc), loc); - RB_OBJ_WRITTEN(p->ast, Qnil, lit); + node = NEW_DSTR0(STRING_NEW0(), 1, NEW_LIST(node, loc), loc); /* fall through */ case NODE_DSTR: nd_set_type(node, NODE_DREGX); nd_set_loc(node, loc); RNODE_DREGX(node)->nd_cflag = options & RE_OPTION_MASK; - if (!NIL_P(RNODE_DREGX(node)->nd_lit)) reg_fragment_check(p, RNODE_DREGX(node)->nd_lit, options); + if (RNODE_DREGX(node)->string) reg_fragment_check(p, RNODE_DREGX(node)->string, options); for (list = RNODE_DREGX(prev = node)->nd_next; list; list = RNODE_LIST(list->nd_next)) { NODE *frag = list->nd_head; enum node_type type = nd_type(frag); if (type == NODE_STR || (type == NODE_DSTR && !RNODE_DSTR(frag)->nd_next)) { - VALUE tail = RNODE_STR(frag)->nd_lit; - if (reg_fragment_check(p, tail, options) && prev && !NIL_P(RNODE_DREGX(prev)->nd_lit)) { - VALUE lit = prev == node ? RNODE_DREGX(prev)->nd_lit : RNODE_LIT(RNODE_LIST(prev)->nd_head)->nd_lit; + rb_parser_string_t *tail = RNODE_STR(frag)->string; + if (reg_fragment_check(p, tail, options) && prev && RNODE_DREGX(prev)->string) { + rb_parser_string_t *lit = prev == node ? RNODE_DREGX(prev)->string : RNODE_STR(RNODE_LIST(prev)->nd_head)->string; if (!literal_concat0(p, lit, tail)) { return NEW_NIL(loc); /* dummy node on error */ } - rb_str_resize(tail, 0); + rb_parser_str_resize(p, tail, 0); RNODE_LIST(prev)->nd_next = list->nd_next; rb_discard_node(p, list->nd_head); rb_discard_node(p, (NODE *)list); @@ -13392,9 +13752,9 @@ new_regexp(struct parser_params *p, NODE *node, int options, const YYLTYPE *loc) } } if (!RNODE_DREGX(node)->nd_next) { - VALUE src = RNODE_DREGX(node)->nd_lit; - VALUE re = reg_compile(p, src, options); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_DREGX(node)->nd_lit = re); + VALUE src = rb_node_dregx_string_val(node); + /* Check string is valid regex */ + reg_compile(p, src, options); } if (options & RE_OPTION_ONCE) { node = NEW_ONCE(node, loc); @@ -13415,9 +13775,7 @@ static NODE * new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) { if (!node) { - VALUE lit = STR_NEW0(); - NODE *xstr = NEW_XSTR(lit, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, lit); + NODE *xstr = NEW_XSTR(STRING_NEW0(), loc); return xstr; } switch (nd_type(node)) { @@ -13430,7 +13788,7 @@ new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) nd_set_loc(node, loc); break; default: - node = NEW_DXSTR(Qnil, 1, NEW_LIST(node, loc), loc); + node = NEW_DXSTR(0, 1, NEW_LIST(node, loc), loc); break; } return node; @@ -13445,9 +13803,6 @@ check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) lit = rb_node_case_when_optimizable_literal(arg); if (UNDEF_P(lit)) return; - if (nd_type_p(arg, NODE_STR)) { - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_STR(arg)->nd_lit = lit); - } if (NIL_P(p->case_labels)) { p->case_labels = rb_obj_hide(rb_hash_new()); @@ -14092,8 +14447,8 @@ shareable_literal_constant(struct parser_params *p, enum shareability shareable, return value; case NODE_STR: - lit = rb_fstring(RNODE_STR(value)->nd_lit); - nd_set_type(value, NODE_LIT); + lit = rb_fstring(rb_node_str_string_val(value)); + value = NEW_LIT(lit, loc); RB_OBJ_WRITE(p->ast, &RNODE_LIT(value)->nd_lit, lit); return value; @@ -15078,11 +15433,11 @@ dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) nd_set_loc(node, loc); break; case NODE_STR: - lit = RNODE_STR(node)->nd_lit; + lit = rb_node_str_string_val(node); node = NEW_SYM(lit, loc); break; default: - node = NEW_DSYM(Qnil, 1, NEW_LIST(node, loc), loc); + node = NEW_DSYM(0, 1, NEW_LIST(node, loc), loc); break; } return node; @@ -15114,7 +15469,7 @@ nd_st_key(struct parser_params *p, NODE *node) case NODE_LIT: return RNODE_LIT(node)->nd_lit; case NODE_STR: - return RNODE_STR(node)->nd_lit; + return rb_node_str_string_val(node); case NODE_INTEGER: return rb_node_integer_literal_val(node); case NODE_FLOAT: @@ -15806,11 +16161,14 @@ reg_fragment_setenc(struct parser_params* p, VALUE str, int options) } static int -reg_fragment_check(struct parser_params* p, VALUE str, int options) -{ - VALUE err; - reg_fragment_setenc(p, str, options); - err = rb_reg_check_preprocess(str); +reg_fragment_check(struct parser_params* p, rb_parser_string_t *str, int options) +{ + VALUE err, str2; + /* TODO */ + str2 = rb_str_new_parser_string(str); + reg_fragment_setenc(p, str2, options); + str->enc = rb_enc_get(str2); + err = rb_reg_check_preprocess(str2); if (err != Qnil) { err = rb_obj_as_string(err); compile_error(p, "%"PRIsVALUE, err); @@ -16417,7 +16775,21 @@ rb_ruby_ripper_parse0(rb_parser_t *p) int rb_ruby_ripper_dedent_string(rb_parser_t *p, VALUE string, int width) { - return dedent_string(p, string, width); + char *str; + long len; + int i; + + RSTRING_GETMEM(string, str, len); + i = dedent_string_column(str, len, width); + if (!i) return 0; + + rb_str_modify(string); + str = RSTRING_PTR(string); + if (RSTRING_LEN(string) != len) + rb_fatal("literal string changed: %+"PRIsVALUE, string); + MEMMOVE(str, str + i, char, len - i); + rb_str_set_len(string, len - i); + return i; } VALUE diff --git a/ruby_parser.c b/ruby_parser.c index 89b2a19d480047..04a49da0ecdfb8 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -166,6 +166,12 @@ mbclen_charfound_p(int len) return MBCLEN_CHARFOUND_P(len); } +static int +mbclen_charfound_len(int len) +{ + return MBCLEN_CHARFOUND_LEN(len); +} + static const char * enc_name(void *enc) { @@ -598,6 +604,7 @@ static const rb_parser_config_t rb_global_parser_config = { .enc_isalnum = enc_isalnum, .enc_precise_mbclen = enc_precise_mbclen, .mbclen_charfound_p = mbclen_charfound_p, + .mbclen_charfound_len = mbclen_charfound_len, .enc_name = enc_name, .enc_prev_char = enc_prev_char, .enc_get = enc_get, @@ -988,6 +995,13 @@ rb_node_imaginary_literal_val(const NODE *n) return lit; } +VALUE +rb_node_str_string_val(const NODE *node) +{ + rb_parser_string_t *str = RNODE_STR(node)->string; + return rb_str_new_parser_string(str); +} + VALUE rb_node_sym_string_val(const NODE *node) { @@ -995,6 +1009,20 @@ rb_node_sym_string_val(const NODE *node) return ID2SYM(rb_intern3(str->ptr, str->len, str->enc)); } +VALUE +rb_node_dstr_string_val(const NODE *node) +{ + rb_parser_string_t *str = RNODE_DSTR(node)->string; + return str ? rb_str_new_parser_string(str) : Qnil; +} + +VALUE +rb_node_dregx_string_val(const NODE *node) +{ + rb_parser_string_t *str = RNODE_DREGX(node)->string; + return rb_str_new_parser_string(str); +} + VALUE rb_node_line_lineno_val(const NODE *node) { diff --git a/rubyparser.h b/rubyparser.h index 704789f08ae478..ab55233d6d71a4 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -37,12 +37,21 @@ /* * Parser String */ +enum rb_parser_string_coderange_type { + /** The object's coderange is unclear yet. */ + RB_PARSER_ENC_CODERANGE_UNKNOWN = 0, + RB_PARSER_ENC_CODERANGE_7BIT = 1, + RB_PARSER_ENC_CODERANGE_VALID = 2, + RB_PARSER_ENC_CODERANGE_BROKEN = 3 +}; + typedef struct rb_parser_string { + enum rb_parser_string_coderange_type coderange; rb_encoding *enc; /* Length of the string, not including terminating NUL character. */ long len; /* Pointer to the contents of the string. */ - char ptr[FLEX_ARY_LEN]; + char *ptr; } rb_parser_string_t; /* @@ -605,7 +614,7 @@ typedef struct RNode_BACK_REF { long nd_nth; } rb_node_back_ref_t; -/* RNode_MATCH, RNode_LIT, RNode_STR and RNode_XSTR should be same structure */ +/* RNode_MATCH and RNode_LIT should be same structure */ typedef struct RNode_MATCH { NODE node; @@ -673,17 +682,18 @@ typedef struct RNode_IMAGINARY { enum rb_numeric_type type; } rb_node_imaginary_t; +/* RNode_STR and RNode_XSTR should be same structure */ typedef struct RNode_STR { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; } rb_node_str_t; /* RNode_DSTR, RNode_DXSTR and RNode_DSYM should be same structure */ typedef struct RNode_DSTR { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; union { long nd_alen; struct RNode *nd_end; /* Second dstr node has this structure. See also RNode_LIST */ @@ -694,13 +704,13 @@ typedef struct RNode_DSTR { typedef struct RNode_XSTR { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; } rb_node_xstr_t; typedef struct RNode_DXSTR { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; long nd_alen; struct RNode_LIST *nd_next; } rb_node_dxstr_t; @@ -714,7 +724,7 @@ typedef struct RNode_EVSTR { typedef struct RNode_DREGX { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; ID nd_cflag; struct RNode_LIST *nd_next; } rb_node_dregx_t; @@ -950,7 +960,7 @@ typedef struct RNode_SYM { typedef struct RNode_DSYM { NODE node; - VALUE nd_lit; + struct rb_parser_string *string; long nd_alen; struct RNode_LIST *nd_next; } rb_node_dsym_t; @@ -1329,6 +1339,7 @@ typedef struct rb_parser_config_struct { int (*enc_isalnum)(OnigCodePoint c, rb_encoding *enc); int (*enc_precise_mbclen)(const char *p, const char *e, rb_encoding *enc); int (*mbclen_charfound_p)(int len); + int (*mbclen_charfound_len)(int len); const char *(*enc_name)(rb_encoding *enc); char *(*enc_prev_char)(const char *s, const char *p, const char *e, rb_encoding *enc); rb_encoding* (*enc_get)(VALUE obj); diff --git a/universal_parser.c b/universal_parser.c index 6ea3f418b02bcc..b670d0f4e3aea1 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -235,6 +235,7 @@ struct rb_imemo_tmpbuf_struct { #define rb_enc_isalnum p->config->enc_isalnum #define rb_enc_precise_mbclen p->config->enc_precise_mbclen #define MBCLEN_CHARFOUND_P p->config->mbclen_charfound_p +#define MBCLEN_CHARFOUND_LEN p->config->mbclen_charfound_len #define rb_enc_name p->config->enc_name #define rb_enc_prev_char p->config->enc_prev_char #define rb_enc_get p->config->enc_get From 007c75ce4c66243e41144c6977e5ccbf4ab71c93 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 9 Feb 2024 14:36:38 +0900 Subject: [PATCH 094/142] Skip to install bundled gems that is C extension and build failed. Ex. We can't build syslog gem in Windows platform. We should skip install syslog as bundled gems. --- tool/rbinstall.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index 2dbc4e9e5bcebf..1001d5a640e29b 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -1061,6 +1061,11 @@ def install_default_gem(dir, srcdir, bindir) skipped[gem_name] = "full name unmatch #{spec.full_name}" next end + # Skip install C ext bundled gem if it is build failed or not found + if !spec.extensions.empty? && !File.exist?("#{build_dir}/#{gem_name}/gem.build_complete") + skipped[gem_name] = "extensions not found or build failed #{spec.full_name}" + next + end spec.extension_dir = "#{extensions_dir}/#{spec.full_name}" package = RbInstall::DirPackage.new spec ins = RbInstall::UnpackedInstaller.new(package, options) From 5e12b757162970b317e2fdf2490b694b52125743 Mon Sep 17 00:00:00 2001 From: Kazuhiro NISHIYAMA Date: Fri, 9 Feb 2024 17:46:24 +0900 Subject: [PATCH 095/142] Remove unavailable filters for merge_group event actionlint says: - "branches" filter is not available for merge_group event. it is only for push, pull_request, pull_request_target, workflow_run events [events] - "paths" filter is not available for merge_group event. it is only for push, pull_request, pull_request_target events [events] - "paths-ignore" filter is not available for merge_group event. it is only for push, pull_request, pull_request_target events [events] --- .github/workflows/annocheck.yml | 7 ------- .github/workflows/baseruby.yml | 7 ------- .github/workflows/bundled_gems.yml | 4 ---- .github/workflows/check_dependencies.yml | 7 ------- .github/workflows/compilers.yml | 7 ------- .github/workflows/macos.yml | 7 ------- .github/workflows/mingw.yml | 8 -------- .github/workflows/prism.yml | 8 -------- .github/workflows/rjit-bindgen.yml | 7 ------- .github/workflows/rjit.yml | 8 -------- .github/workflows/spec_guards.yml | 3 --- .github/workflows/ubuntu.yml | 7 ------- .github/workflows/wasm.yml | 7 ------- .github/workflows/windows.yml | 7 ------- .github/workflows/yjit-macos.yml | 7 ------- .github/workflows/yjit-ubuntu.yml | 7 ------- 16 files changed, 108 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index da39efa9e36c1b..bc72d751e20549 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -18,13 +18,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index de10d9a075fd2b..2fac7c4dc94014 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -18,13 +18,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 943140e9ef9713..81d4a2a9dc0e36 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -12,10 +12,6 @@ on: - '.github/workflows/bundled_gems.yml' - 'gems/bundled_gems' merge_group: - branches: ['master'] - paths: - - '.github/workflows/bundled_gems.yml' - - 'gems/bundled_gems' schedule: - cron: '45 6 * * *' workflow_dispatch: diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 10c202c3c132a2..5d36d440598689 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -17,13 +17,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index c66249048223a4..48c1ecc21c010b 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -18,13 +18,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1f93580c4eaeda..c7f57385b32005 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -12,13 +12,6 @@ on: # Do not use paths-ignore for required status checks # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index e159916ed2e399..2a4eacd6d7fd5e 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -19,14 +19,6 @@ on: - '.*.yml' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 151fd2c4966d3a..3d54b2b6a9bb94 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -19,14 +19,6 @@ on: - '**.ronn' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/rjit-bindgen.yml b/.github/workflows/rjit-bindgen.yml index ba42da26a8b2c7..24d09a8ee7973d 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -17,13 +17,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/rjit.yml b/.github/workflows/rjit.yml index bf9122f5810494..86610d6bc009a6 100644 --- a/.github/workflows/rjit.yml +++ b/.github/workflows/rjit.yml @@ -19,14 +19,6 @@ on: - '**.ronn' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 41261840beea2a..248497041a06df 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -10,9 +10,6 @@ on: - 'spec/**' - '!spec/*.md' merge_group: - paths: - - 'spec/**' - - '!spec/*.md' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b78570e22479c0..7e13291f3607c7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -12,13 +12,6 @@ on: # Do not use paths-ignore for required status checks # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 7bf35b333d5921..e066463c54bb9d 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -17,13 +17,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index aeafa8393096e8..0776857589c5a6 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -17,13 +17,6 @@ on: - '**/.document' - '.*.yml' merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 93bbb4d93ed587..3c9b94e1baaf1a 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -12,13 +12,6 @@ on: # Do not use paths-ignore for required status checks # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 437f3d53397083..850e534b680d32 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -12,13 +12,6 @@ on: # Do not use paths-ignore for required status checks # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks merge_group: - paths-ignore: - - 'doc/**' - - '**/man' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} From 2c6767b71ef5154f49e4aef7a236849a934e68fb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 12:59:43 +0900 Subject: [PATCH 096/142] [ruby/optparse] Respect default values in block parameters Fix https://github.com/ruby/optparse/pull/55 https://github.com/ruby/optparse/commit/9d53e74aa4 --- lib/optparse.rb | 24 +++++++++++++++--------- test/optparse/test_acceptable.rb | 5 +++++ test/optparse/test_optarg.rb | 8 ++++++++ test/optparse/test_optparse.rb | 7 +++++++ test/optparse/test_placearg.rb | 10 ++++++++++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ccfea6dcf0f668..d363b910efc974 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -697,6 +697,11 @@ def pretty_print(q) # :nodoc: q.object_group(self) {pretty_print_contents(q)} end + def omitted_argument(val) # :nodoc: + val.pop if val.size == 3 and val.last.nil? + val + end + # # Switch that takes no arguments. # @@ -755,7 +760,7 @@ def parse(arg, argv, &error) if arg conv_arg(*parse_arg(arg, &error)) else - conv_arg(arg) + omitted_argument conv_arg(arg) end end @@ -774,13 +779,14 @@ class PlacedArgument < self # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) - return nil, block, nil + return nil, block end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else + omitted_argument val val[0] = nil end val @@ -1633,7 +1639,7 @@ def order(*argv, into: nil, &nonopt) # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, &nonopt) - setter = ->(name, val) {into[name.to_sym] = val} if into + setter = ->(name, val = nil) {into[name.to_sym] = val} if into parse_in_order(argv, setter, &nonopt) end @@ -1658,9 +1664,9 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: raise $!.set_option(arg, true) end begin - opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} - val = cb.call(val) if cb - setter.call(sw.switch_name, val) if setter + opt, cb, *val = sw.parse(rest, argv) {|*exc| raise(*exc)} + val = cb.call(*val) if cb + setter.call(sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, rest) end @@ -1690,7 +1696,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: raise $!.set_option(arg, true) end begin - opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + opt, cb, *val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} rescue ParseError raise $!.set_option(arg, arg.length > 2) else @@ -1698,8 +1704,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: end begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') - val = cb.call(val) if cb - setter.call(sw.switch_name, val) if setter + val = cb.call(*val) if cb + setter.call(sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end diff --git a/test/optparse/test_acceptable.rb b/test/optparse/test_acceptable.rb index 12f5322726b053..c7ea2152fc0d40 100644 --- a/test/optparse/test_acceptable.rb +++ b/test/optparse/test_acceptable.rb @@ -8,6 +8,7 @@ def setup @opt.def_option("--integer VAL", Integer) { |v| @integer = v } @opt.def_option("--float VAL", Float) { |v| @float = v } @opt.def_option("--numeric VAL", Numeric) { |v| @numeric = v } + @opt.def_option("--array VAL", Array) { |v| @array = v } @opt.def_option("--decimal-integer VAL", OptionParser::DecimalInteger) { |i| @decimal_integer = i } @@ -195,4 +196,8 @@ def test_decimal_numeric end end + def test_array + assert_equal(%w"", no_error {@opt.parse!(%w"--array a,b,c")}) + assert_equal(%w"a b c", @array) + end end diff --git a/test/optparse/test_optarg.rb b/test/optparse/test_optarg.rb index 81127a8a3757c0..f4882b0ea78e76 100644 --- a/test/optparse/test_optarg.rb +++ b/test/optparse/test_optarg.rb @@ -9,6 +9,7 @@ def setup @opt.def_option("--regexp[=REGEXP]", Regexp) {|x| @reopt = x} @opt.def_option "--with_underscore[=VAL]" do |x| @flag = x end @opt.def_option "--with-hyphen[=VAL]" do |x| @flag = x end + @opt.def_option("--fallback[=VAL]") do |x = "fallback"| @flag = x end @reopt = nil end @@ -57,4 +58,11 @@ def test_hyphenize assert_equal(%w"", no_error {@opt.parse!(%w"--with_hyphen=foo4")}) assert_equal("foo4", @flag) end + + def test_default_argument + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback=val1")}) + assert_equal("val1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) + assert_equal("fallback", @flag) + end end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index be9bcb8425b26e..3b9ccc756ace06 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -73,10 +73,17 @@ def test_into @opt.def_option "-p", "--port=PORT", "port", Integer @opt.def_option "-v", "--verbose" do @verbose = true end @opt.def_option "-q", "--quiet" do @quiet = true end + @opt.def_option "-o", "--option [OPT]" do |opt| @option = opt end result = {} @opt.parse %w(--host localhost --port 8000 -v), into: result assert_equal({host: "localhost", port: 8000, verbose: true}, result) assert_equal(true, @verbose) + result = {} + @opt.parse %w(--option -q), into: result + assert_equal({quiet: true, option: nil}, result) + result = {} + @opt.parse %w(--option OPTION -v), into: result + assert_equal({verbose: true, option: "OPTION"}, result) end def test_require_exact diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index ed0e4d3e6c61e0..56b641b0b6d7f0 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -13,6 +13,7 @@ def setup @reopt = nil @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end + @opt.def_option("--fallback [VAL]") do |x = "fallback"| @flag = x end end def test_short @@ -73,4 +74,13 @@ def test_conv assert_equal(%w"te.rb", no_error('[ruby-dev:38333]') {@opt.parse!(%w"-T1 te.rb")}) assert_equal(1, @topt) end + + def test_default_argument + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback=val1")}) + assert_equal("val1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback val2")}) + assert_equal("val2", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) + assert_equal("fallback", @flag) + end end From db73226bf6aa8c67ad6976ff6b3c628cf6b8a952 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 19:03:20 +0900 Subject: [PATCH 097/142] [ruby/optparse] Adjust arguments for lambda-callbacks Rake uses [lambda] as callbacks. Calling it without omitted argument raises an `ArgumentError`. lambda: https://github.com/ruby/rake/blob/master/lib/rake/application.rb#L543 https://github.com/ruby/optparse/commit/213cb03b59 --- lib/optparse.rb | 21 ++++++++++++++++----- test/optparse/test_optarg.rb | 8 ++++++++ test/optparse/test_placearg.rb | 10 ++++++++++ test/optparse/test_reqarg.rb | 6 ++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index d363b910efc974..fbcd7f9746f756 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1639,7 +1639,7 @@ def order(*argv, into: nil, &nonopt) # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, &nonopt) - setter = ->(name, val = nil) {into[name.to_sym] = val} if into + setter = ->(name, val) {into[name.to_sym] = val} if into parse_in_order(argv, setter, &nonopt) end @@ -1665,8 +1665,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: end begin opt, cb, *val = sw.parse(rest, argv) {|*exc| raise(*exc)} - val = cb.call(*val) if cb - setter.call(sw.switch_name, *val) if setter + val = callback!(cb, 1, *val) if cb + callback!(setter, 2, sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, rest) end @@ -1704,8 +1704,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: end begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') - val = cb.call(*val) if cb - setter.call(sw.switch_name, *val) if setter + val = callback!(cb, 1, *val) if cb + callback!(setter, 2, sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end @@ -1731,6 +1731,17 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: end private :parse_in_order + # Calls callback with _val_. + def callback!(cb, max_arity, *args) # :nodoc: + if (size = args.size) < max_arity and cb.to_proc.lambda? + (arity = cb.arity) < 0 and arity = (1-arity) + arity = max_arity if arity > max_arity + args[arity - 1] = nil if arity > size + end + cb.call(*args) + end + private :callback! + # # Parses command line arguments +argv+ in permutation mode and returns # list of non-option arguments. When optional +into+ keyword diff --git a/test/optparse/test_optarg.rb b/test/optparse/test_optarg.rb index f4882b0ea78e76..f94460527fdeb1 100644 --- a/test/optparse/test_optarg.rb +++ b/test/optparse/test_optarg.rb @@ -10,6 +10,7 @@ def setup @opt.def_option "--with_underscore[=VAL]" do |x| @flag = x end @opt.def_option "--with-hyphen[=VAL]" do |x| @flag = x end @opt.def_option("--fallback[=VAL]") do |x = "fallback"| @flag = x end + @opt.def_option("--lambda[=VAL]", &->(x) {@flag = x}) @reopt = nil end @@ -65,4 +66,11 @@ def test_default_argument assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) assert_equal("fallback", @flag) end + + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")}) + assert_equal(nil, @flag) + end end diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index 56b641b0b6d7f0..a8a11e676b82be 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -14,6 +14,7 @@ def setup @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end @opt.def_option("--fallback [VAL]") do |x = "fallback"| @flag = x end + @opt.def_option("--lambda [VAL]", &->(x) {@flag = x}) end def test_short @@ -83,4 +84,13 @@ def test_default_argument assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) assert_equal("fallback", @flag) end + + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda lambda2")}) + assert_equal("lambda2", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")}) + assert_equal(nil, @flag) + end end diff --git a/test/optparse/test_reqarg.rb b/test/optparse/test_reqarg.rb index d5686d13aa8acd..31d4fef417ff21 100644 --- a/test/optparse/test_reqarg.rb +++ b/test/optparse/test_reqarg.rb @@ -6,6 +6,7 @@ def setup super @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end + @opt.def_option("--lambda=VAL", &->(x) {@flag = x}) end class Def1 < TestOptionParser @@ -81,6 +82,11 @@ def test_hyphenize assert_equal("foo4", @flag) end + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + end + class TestOptionParser::WithPattern < TestOptionParser def test_pattern pat = num = nil From 08b77dd682f342943bb64f9d823040cd350240ba Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 8 Feb 2024 21:56:26 +0000 Subject: [PATCH 098/142] Remove unused bind argument from eval_make_iseq --- vm_eval.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm_eval.c b/vm_eval.c index 3322cf8451ea65..105373126066c9 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1643,7 +1643,7 @@ get_eval_default_path(void) } static const rb_iseq_t * -eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, +eval_make_iseq(VALUE src, VALUE fname, int line, const struct rb_block *base_block) { const VALUE parser = rb_parser_new(); @@ -1724,7 +1724,7 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li block.as.captured.code.iseq = cfp->iseq; block.type = block_type_iseq; - iseq = eval_make_iseq(src, file, line, NULL, &block); + iseq = eval_make_iseq(src, file, line, &block); if (!iseq) { rb_exc_raise(ec->errinfo); } @@ -1745,7 +1745,7 @@ eval_string_with_scope(VALUE scope, VALUE src, VALUE file, int line) { rb_execution_context_t *ec = GET_EC(); rb_binding_t *bind = Check_TypedStruct(scope, &ruby_binding_data_type); - const rb_iseq_t *iseq = eval_make_iseq(src, file, line, bind, &bind->block); + const rb_iseq_t *iseq = eval_make_iseq(src, file, line, &bind->block); if (!iseq) { rb_exc_raise(ec->errinfo); } From a4ba62b6e5954f1826bea7027808f72bd22c874f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 11:29:36 -0500 Subject: [PATCH 099/142] [PRISM] Refactor case nodes for only one pass through when --- prism_compile.c | 128 ++++++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 19a2d9db4d0b4b..3c42ab07e62ee2 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -4463,76 +4463,108 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_CASE_NODE: { - pm_case_node_t *case_node = (pm_case_node_t *)node; - bool has_predicate = case_node->predicate; - if (has_predicate) { - PM_COMPILE_NOT_POPPED(case_node->predicate); - } - LABEL *end_label = NEW_LABEL(lineno); + // case foo; when bar; end + // ^^^^^^^^^^^^^^^^^^^^^^^ + const pm_case_node_t *cast = (const pm_case_node_t *) node; + const pm_node_list_t *conditions = &cast->conditions; + bool has_predicate = cast->predicate != NULL; + + // This is the anchor that we will compile the conditions of the various + // `when` nodes into. If a match is found, they will need to jump into + // the body_seq anchor to the correct spot. + DECL_ANCHOR(cond_seq); + INIT_ANCHOR(cond_seq); - pm_node_list_t conditions = case_node->conditions; + // This is the anchor that we will compile the bodies of the various + // `when` nodes into. We'll make sure that the clauses that are compiled + // jump into the correct spots within this anchor. + DECL_ANCHOR(body_seq); + INIT_ANCHOR(body_seq); - LABEL **conditions_labels = (LABEL **)ALLOCA_N(VALUE, conditions.size + 1); - LABEL *label; + // This is the label where all of the when clauses will jump to if they + // have matched and are done executing their bodies. + LABEL *end_label = NEW_LABEL(lineno); - for (size_t i = 0; i < conditions.size; i++) { - label = NEW_LABEL(lineno); - conditions_labels[i] = label; - pm_when_node_t *when_node = (pm_when_node_t *)conditions.nodes[i]; - - for (size_t i = 0; i < when_node->conditions.size; i++) { - pm_node_t *condition_node = when_node->conditions.nodes[i]; - - if (PM_NODE_TYPE_P(condition_node, PM_SPLAT_NODE)) { - int checkmatch_type = has_predicate ? VM_CHECKMATCH_TYPE_CASE : VM_CHECKMATCH_TYPE_WHEN; - ADD_INSN (ret, &dummy_line_node, dup); - PM_COMPILE_NOT_POPPED(condition_node); - ADD_INSN1(ret, &dummy_line_node, checkmatch, - INT2FIX(checkmatch_type | VM_CHECKMATCH_ARRAY)); + // We're going to loop through each of the conditions in the case node + // and compile each of their contents into both the cond_seq and the + // body_seq. Each condition will use its own label to jump from its + // conditions into its body. + // + // Note that none of the code in the loop below should be adding + // anything to ret, as we're going to be laying out the entire case node + // instructions later. + for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) { + const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index]; + const pm_node_list_t *conditions = &clause->conditions; + + LABEL *label = NEW_LABEL(lineno); + + // Compile each of the conditions for the when clause into the + // cond_seq. Each one should have a unique comparison that then + // jumps into the body if it matches. + for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) { + const pm_node_t *condition = conditions->nodes[condition_index]; + + if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { + ADD_INSN(cond_seq, &dummy_line_node, dup); + pm_compile_node(iseq, condition, cond_seq, false, scope_node); + + int type = has_predicate ? VM_CHECKMATCH_TYPE_CASE : VM_CHECKMATCH_TYPE_WHEN; + ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(type | VM_CHECKMATCH_ARRAY)); } else { - PM_COMPILE_NOT_POPPED(condition_node); + pm_compile_node(iseq, condition, cond_seq, false, scope_node); + if (has_predicate) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); + ADD_INSN1(cond_seq, &dummy_line_node, topn, INT2FIX(1)); + ADD_SEND_WITH_FLAG(cond_seq, &dummy_line_node, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); } } - ADD_INSNL(ret, &dummy_line_node, branchif, label); + ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); + } + + // Now, add the label to the body and compile the body of the when + // clause. This involves popping the predicate if there was one, + // compiling the statements to be executed, and then compiling a + // jump to the end of the case node. + ADD_LABEL(body_seq, label); + if (has_predicate) { + ADD_INSN(body_seq, &dummy_line_node, pop); + } + + if (clause->statements != NULL) { + pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); } + else if (!popped) { + ADD_INSN(body_seq, &dummy_line_node, putnil); + } + + ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); } + // Now that we have compiled the conditions and the bodies of the + // various when clauses, we can compile the predicate, lay out the + // conditions, compile the fallback consequent if there is one, and + // finally put in the bodies of the when clauses. + if (has_predicate) { + PM_COMPILE_NOT_POPPED(cast->predicate); + } + + ADD_SEQ(ret, cond_seq); if (has_predicate) { PM_POP; } - if (case_node->consequent) { - PM_COMPILE((pm_node_t *)case_node->consequent); + if (cast->consequent != NULL) { + PM_COMPILE((const pm_node_t *) cast->consequent); } else { PM_PUTNIL_UNLESS_POPPED; } ADD_INSNL(ret, &dummy_line_node, jump, end_label); - - for (size_t i = 0; i < conditions.size; i++) { - label = conditions_labels[i]; - ADD_LABEL(ret, label); - if (has_predicate) { - PM_POP; - } - - pm_while_node_t *condition_node = (pm_while_node_t *)conditions.nodes[i]; - if (condition_node->statements) { - PM_COMPILE((pm_node_t *)condition_node->statements); - } - else { - PM_PUTNIL_UNLESS_POPPED; - } - - ADD_INSNL(ret, &dummy_line_node, jump, end_label); - } - + ADD_SEQ(ret, body_seq); ADD_LABEL(ret, end_label); return; } From 5c2d96df194abcb7d9d6f154635c30f7d8811c13 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 12:00:19 -0500 Subject: [PATCH 100/142] [PRISM] Implement opt_case_dispatch --- prism_compile.c | 265 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 203 insertions(+), 62 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index 3c42ab07e62ee2..b2e5c385870604 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -4025,6 +4025,54 @@ pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co } } +/** + * When we're compiling a case node, it's possible that we can speed it up using + * a dispatch hash, which will allow us to jump directly to the correct when + * clause body based on a hash lookup of the value. This can only happen when + * the conditions are literals that can be compiled into a hash key. + * + * This function accepts a dispatch hash and the condition of a when clause. It + * is responsible for compiling the condition into a hash key and then adding it + * to the dispatch hash. + * + * If the value can be successfully compiled into the hash, then this function + * returns the dispatch hash with the new key added. If the value cannot be + * compiled into the hash, then this function returns Qundef. In the case of + * Qundef, this function is signaling that the caller should abandon the + * optimization entirely. + */ +static VALUE +pm_compile_case_node_dispatch(VALUE dispatch, const pm_node_t *node, LABEL *label, const pm_scope_node_t *scope_node) +{ + VALUE key = Qundef; + + switch (PM_NODE_TYPE(node)) { + case PM_FALSE_NODE: + case PM_FLOAT_NODE: + case PM_INTEGER_NODE: + case PM_NIL_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_SYMBOL_NODE: + case PM_TRUE_NODE: + key = pm_static_literal_value(node, scope_node, scope_node->parser); + break; + case PM_STRING_NODE: { + const pm_string_node_t *cast = (const pm_string_node_t *) node; + key = rb_fstring(parse_string_encoded(node, &cast->unescaped, scope_node->parser)); + break; + } + default: + return Qundef; + } + + if (NIL_P(rb_hash_lookup(dispatch, key))) { + rb_hash_aset(dispatch, key, ((VALUE) label) | 1); + } + + return dispatch; +} + /* * Compiles a prism node into instruction sequences * @@ -4467,7 +4515,6 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^^^^^^^^^^^^^^^^^^^ const pm_case_node_t *cast = (const pm_case_node_t *) node; const pm_node_list_t *conditions = &cast->conditions; - bool has_predicate = cast->predicate != NULL; // This is the anchor that we will compile the conditions of the various // `when` nodes into. If a match is found, they will need to jump into @@ -4485,87 +4532,181 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // have matched and are done executing their bodies. LABEL *end_label = NEW_LABEL(lineno); - // We're going to loop through each of the conditions in the case node - // and compile each of their contents into both the cond_seq and the - // body_seq. Each condition will use its own label to jump from its - // conditions into its body. - // - // Note that none of the code in the loop below should be adding - // anything to ret, as we're going to be laying out the entire case node - // instructions later. - for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) { - const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index]; - const pm_node_list_t *conditions = &clause->conditions; - - LABEL *label = NEW_LABEL(lineno); - - // Compile each of the conditions for the when clause into the - // cond_seq. Each one should have a unique comparison that then - // jumps into the body if it matches. - for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) { - const pm_node_t *condition = conditions->nodes[condition_index]; - - if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { - ADD_INSN(cond_seq, &dummy_line_node, dup); - pm_compile_node(iseq, condition, cond_seq, false, scope_node); - - int type = has_predicate ? VM_CHECKMATCH_TYPE_CASE : VM_CHECKMATCH_TYPE_WHEN; - ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(type | VM_CHECKMATCH_ARRAY)); + // If we have a predicate on this case statement, then it's going to + // compare all of the various when clauses to the predicate. If we + // don't, then it's basically an if-elsif-else chain. + if (cast->predicate == NULL) { + // Loop through each clauses in the case node and compile each of + // the conditions within them into cond_seq. If they match, they + // should jump into their respective bodies in body_seq. + for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) { + const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index]; + const pm_node_list_t *conditions = &clause->conditions; + + int clause_lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, clause->base.location.start).line; + LABEL *label = NEW_LABEL(clause_lineno); + + ADD_LABEL(body_seq, label); + if (clause->statements != NULL) { + pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); } - else { - pm_compile_node(iseq, condition, cond_seq, false, scope_node); - - if (has_predicate) { - ADD_INSN1(cond_seq, &dummy_line_node, topn, INT2FIX(1)); - ADD_SEND_WITH_FLAG(cond_seq, &dummy_line_node, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); - } + else if (!popped) { + ADD_INSN(body_seq, &dummy_line_node, putnil); } - ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); - } + ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); - // Now, add the label to the body and compile the body of the when - // clause. This involves popping the predicate if there was one, - // compiling the statements to be executed, and then compiling a - // jump to the end of the case node. - ADD_LABEL(body_seq, label); - if (has_predicate) { - ADD_INSN(body_seq, &dummy_line_node, pop); + // Compile each of the conditions for the when clause into the + // cond_seq. Each one should have a unique condition and should + // jump to the subsequent one if it doesn't match. + for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) { + const pm_node_t *condition = conditions->nodes[condition_index]; + + if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { + ADD_INSN(cond_seq, &dummy_line_node, putnil); + pm_compile_node(iseq, condition, cond_seq, false, scope_node); + ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY)); + ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); + } + else { + int condition_lineno = (int) pm_newline_list_line_column(&scope_node->parser->newline_list, condition->location.start).line; + LABEL *next_label = NEW_LABEL(condition_lineno); + + pm_compile_branch_condition(iseq, cond_seq, condition, label, next_label, false, scope_node); + ADD_LABEL(cond_seq, next_label); + } + } } - if (clause->statements != NULL) { - pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); + // Compile the consequent else clause if there is one. + if (cast->consequent) { + pm_compile_node(iseq, (const pm_node_t *) cast->consequent, cond_seq, popped, scope_node); } else if (!popped) { - ADD_INSN(body_seq, &dummy_line_node, putnil); + ADD_INSN(cond_seq, &dummy_line_node, putnil); } - ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); + // Finally, jump to the end label if none of the other conditions + // have matched. + ADD_INSNL(cond_seq, &dummy_line_node, jump, end_label); + ADD_SEQ(ret, cond_seq); } + else { + // This is the label where everything will fall into if none of the + // conditions matched. + LABEL *else_label = NEW_LABEL(lineno); + + // It's possible for us to speed up the case node by using a + // dispatch hash. This is a hash that maps the conditions of the + // various when clauses to the labels of their bodies. If we can + // compile the conditions into a hash key, then we can use a hash + // lookup to jump directly to the correct when clause body. + VALUE dispatch = Qundef; + if (ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { + dispatch = rb_hash_new(); + RHASH_TBL_RAW(dispatch)->type = &cdhash_type; + } + + // We're going to loop through each of the conditions in the case + // node and compile each of their contents into both the cond_seq + // and the body_seq. Each condition will use its own label to jump + // from its conditions into its body. + // + // Note that none of the code in the loop below should be adding + // anything to ret, as we're going to be laying out the entire case + // node instructions later. + for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) { + const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index]; + const pm_node_list_t *conditions = &clause->conditions; + + LABEL *label = NEW_LABEL(lineno); + + // Compile each of the conditions for the when clause into the + // cond_seq. Each one should have a unique comparison that then + // jumps into the body if it matches. + for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) { + const pm_node_t *condition = conditions->nodes[condition_index]; + + // If we haven't already abandoned the optimization, then + // we're going to try to compile the condition into the + // dispatch hash. + if (dispatch != Qundef) { + dispatch = pm_compile_case_node_dispatch(dispatch, condition, label, scope_node); + } + + if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { + ADD_INSN(cond_seq, &dummy_line_node, dup); + pm_compile_node(iseq, condition, cond_seq, false, scope_node); + ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY)); + } + 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((const pm_node_t *) string, &string->unescaped, parser)); + ADD_INSN1(cond_seq, &dummy_line_node, putobject, value); + } + else { + pm_compile_node(iseq, condition, cond_seq, false, scope_node); + } + + ADD_INSN1(cond_seq, &dummy_line_node, topn, INT2FIX(1)); + ADD_SEND_WITH_FLAG(cond_seq, &dummy_line_node, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); + } + + ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); + } + + // Now, add the label to the body and compile the body of the + // when clause. This involves popping the predicate, compiling + // the statements to be executed, and then compiling a jump to + // the end of the case node. + ADD_LABEL(body_seq, label); + ADD_INSN(body_seq, &dummy_line_node, pop); + + if (clause->statements != NULL) { + pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); + } + else if (!popped) { + ADD_INSN(body_seq, &dummy_line_node, putnil); + } - // Now that we have compiled the conditions and the bodies of the - // various when clauses, we can compile the predicate, lay out the - // conditions, compile the fallback consequent if there is one, and - // finally put in the bodies of the when clauses. - if (has_predicate) { + ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); + } + + // Now that we have compiled the conditions and the bodies of the + // various when clauses, we can compile the predicate, lay out the + // conditions, compile the fallback consequent if there is one, and + // finally put in the bodies of the when clauses. PM_COMPILE_NOT_POPPED(cast->predicate); - } - ADD_SEQ(ret, cond_seq); - if (has_predicate) { + // If we have a dispatch hash, then we'll use it here to create the + // optimization. + if (dispatch != Qundef) { + PM_DUP; + ADD_INSN2(ret, &dummy_line_node, opt_case_dispatch, dispatch, else_label); + LABEL_REF(else_label); + } + + ADD_SEQ(ret, cond_seq); + + // Compile either the explicit else clause or an implicit else + // clause. + ADD_LABEL(ret, else_label); PM_POP; - } - if (cast->consequent != NULL) { - PM_COMPILE((const pm_node_t *) cast->consequent); - } - else { - PM_PUTNIL_UNLESS_POPPED; + if (cast->consequent != NULL) { + PM_COMPILE((const pm_node_t *) cast->consequent); + } + else if (!popped) { + PM_PUTNIL; + } + + ADD_INSNL(ret, &dummy_line_node, jump, end_label); } - ADD_INSNL(ret, &dummy_line_node, jump, end_label); ADD_SEQ(ret, body_seq); ADD_LABEL(ret, end_label); + return; } case PM_CASE_MATCH_NODE: { From cf1cd215c0a057da123ec9753091154230b3dc97 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 9 Feb 2024 11:16:03 -0500 Subject: [PATCH 101/142] [ruby/prism] Significantly faster offset cache for parser https://github.com/ruby/prism/commit/8cd92eef79 --- lib/prism/translation/parser.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 6723216d0057c2..6e678dde6b4088 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -124,20 +124,21 @@ def unwrap(result, offset_cache) # build the parser gem AST. # # If the bytesize of the source is the same as the length, then we can - # just use the offset directly. Otherwise, we build a hash that functions - # as a cache for the conversion. - # - # This is a good opportunity for some optimizations. If the source file - # has any multi-byte characters, this can tank the performance of the - # translator. We could make this significantly faster by using a - # different data structure for the cache. + # just use the offset directly. Otherwise, we build an array where the + # index is the byte offset and the value is the character offset. def build_offset_cache(source) if source.bytesize == source.length -> (offset) { offset } else - Hash.new do |hash, offset| - hash[offset] = source.byteslice(0, offset).length + offset_cache = [] + offset = 0 + + source.each_char do |char| + char.bytesize.times { offset_cache << offset } + offset += 1 end + + offset_cache << offset end end From d19d683a354530a27b4cbb049223f8dc70c75849 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 9 Feb 2024 13:54:06 +0100 Subject: [PATCH 102/142] rb_obj_setup: do not copy RUBY_FL_SEEN_OBJ_ID [Bug #20250] We're seting up a new instance, so it never had an associated object_id. --- object.c | 2 +- test/ruby/test_clone.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/object.c b/object.c index a0783e9ef76e90..26ced2c8d89734 100644 --- a/object.c +++ b/object.c @@ -120,7 +120,7 @@ rb_obj_reveal(VALUE obj, VALUE klass) VALUE rb_obj_setup(VALUE obj, VALUE klass, VALUE type) { - VALUE ignored_flags = RUBY_FL_PROMOTED; + VALUE ignored_flags = RUBY_FL_PROMOTED | RUBY_FL_SEEN_OBJ_ID; RBASIC(obj)->flags = (type & ~ignored_flags) | (RBASIC(obj)->flags & ignored_flags); RBASIC_SET_CLASS(obj, klass); return obj; diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb index 216eaa39d25b12..775c9ed8481703 100644 --- a/test/ruby/test_clone.rb +++ b/test/ruby/test_clone.rb @@ -73,6 +73,13 @@ def test_frozen_properties_and_ivars_retained_on_clone_with_ivar assert_equal(cloned_obj.instance_variable_get(:@a), 1) end + def test_proc_obj_id_flag_reset + # [Bug #20250] + proc = Proc.new { } + proc.object_id + proc.clone.object_id # Would crash with RUBY_DEBUG=1 + end + def test_user_flags assert_separately([], <<-EOS) # From 80490acfb601557497d5c10841fc5493e691e6da Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 9 Feb 2024 11:08:54 -0600 Subject: [PATCH 103/142] More on IO doc (#9842) --- io.c | 146 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/io.c b/io.c index eb2698d0479b31..621d5c84572d25 100644 --- a/io.c +++ b/io.c @@ -15094,56 +15094,64 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * * == Line \IO * - * You can read an \IO stream line-by-line using these methods: + * \Class \IO supports line-oriented + * {input}[rdoc-ref:IO@Line+Input] and {output}[rdoc-ref:IO@Line+Output] * - * - IO#each_line: Reads each remaining line, passing it to the given block. - * - IO#gets: Returns the next line. - * - IO#readline: Like #gets, but raises an exception at end-of-stream. - * - IO#readlines: Returns all remaining lines in an array. + * === Line Input + * + * \Class \IO supports line-oriented input for + * {files}[rdoc-ref:IO@File+Line+Input] and {IO streams}[rdoc-ref:IO@Stream+Line+Input] + * + * ==== \File Line Input * - * Each of these reader methods accepts: + * You can read lines from a file using these methods: * - * - An optional line separator, +sep+; + * - IO.foreach: Reads each line and passes it to the given block. + * - IO.readlines: Reads and returns all lines in an array. + * + * For each of these methods: + * + * - You can specify {open options}[rdoc-ref:IO@Open+Options]. + * - Line parsing depends on the effective line separator; * see {Line Separator}[rdoc-ref:IO@Line+Separator]. - * - An optional line-size limit, +limit+; + * - The length of each returned line depends on the effective line limit; * see {Line Limit}[rdoc-ref:IO@Line+Limit]. * - * For each of these reader methods, reading may begin mid-line, - * depending on the stream's position; - * see {Position}[rdoc-ref:IO@Position]: + * ==== Stream Line Input * - * f = File.new('t.txt') - * f.pos = 27 - * f.each_line {|line| p line } - * f.close + * You can read lines from an \IO stream using these methods: * - * Output: - * - * "rth line\n" - * "Fifth line\n" + * - IO#each_line: Reads each remaining line, passing it to the given block. + * - IO#gets: Returns the next line. + * - IO#readline: Like #gets, but raises an exception at end-of-stream. + * - IO#readlines: Returns all remaining lines in an array. * - * You can write to an \IO stream line-by-line using this method: + * For each of these methods: * - * - IO#puts: Writes objects to the stream. + * - Reading may begin mid-line, + * depending on the stream's _position_; + * see {Position}[rdoc-ref:IO@Position]. + * - Line parsing depends on the effective line separator; + * see {Line Separator}[rdoc-ref:IO@Line+Separator]. + * - The length of each returned line depends on the effective line limit; + * see {Line Limit}[rdoc-ref:IO@Line+Limit]. * - * === Line Separator + * ===== Line Separator * - * Each of these methods uses a line separator, - * which is the string that delimits lines: + * Each of the {line input methods}[rdoc-ref:IO@Line+Input] uses a line separator: + * the string that determines what is considered a line; + * it is sometimes called the input record separator. * - * - IO.foreach. - * - IO.readlines. - * - IO#each_line. - * - IO#gets. - * - IO#readline. - * - IO#readlines. + * The default line separator is taken from global variable $/, + * whose initial value is "\n". * - * The default line separator is the given by the global variable $/, - * whose value is by default "\n". - * The line to be read next is all data from the current position - * to the next line separator: + * Generally, the line to be read next is all data + * from the current {position}[rdoc-ref:IO@Position] + * to the next line separator + * (but see {Special Line Separator Values}[rdoc-ref:IO@Special+Line+Separator+Values]): * * f = File.new('t.txt') + * # Method gets with no sep argument returns the next line, according to $/. * f.gets # => "First line\n" * f.gets # => "Second line\n" * f.gets # => "\n" @@ -15151,7 +15159,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * f.gets # => "Fifth line\n" * f.close * - * You can specify a different line separator: + * You can use a different line separator by passing argument +sep+: * * f = File.new('t.txt') * f.gets('l') # => "First l" @@ -15160,15 +15168,27 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * f.gets # => "e\n" * f.close * - * There are two special line separators: + * Or by setting global variable $/: + * + * f = File.new('t.txt') + * $/ = 'l' + * f.gets # => "First l" + * f.gets # => "ine\nSecond l" + * f.gets # => "ine\n\nFourth l" + * f.close + * + * ===== Special Line Separator Values + * + * Each of the {line input methods}[rdoc-ref:IO@Line+Input] + * accepts two special values for parameter +sep+: * - * - +nil+: The entire stream is read into a single string: + * - +nil+: The entire stream is to be read ("slurped") into a single string: * * f = File.new('t.txt') * f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" * f.close * - * - '' (the empty string): The next "paragraph" is read + * - '' (the empty string): The next "paragraph" is to be read * (paragraphs being separated by two consecutive line separators): * * f = File.new('t.txt') @@ -15176,23 +15196,18 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * f.gets('') # => "Fourth line\nFifth line\n" * f.close * - * === Line Limit + * ===== Line Limit * - * Each of these methods uses a line limit, - * which specifies that the number of bytes returned may not be (much) longer - * than the given +limit+; + * Each of the {line input methods}[rdoc-ref:IO@Line+Input] + * uses an integer line limit, + * which restricts the number of bytes that may be returned. + * (A multi-byte character will not be split, and so a returned line may be slightly longer + * than the limit). * - * - IO.foreach. - * - IO.readlines. - * - IO#each_line. - * - IO#gets. - * - IO#readline. - * - IO#readlines. + * The default limit value is -1; + * any negative limit value means that there is no limit. * - * A multi-byte character will not be split, and so a line may be slightly longer - * than the given limit. - * - * If +limit+ is not given, the line is determined only by +sep+. + * If there is no limit, the line is determined only by +sep+. * * # Text with 1-byte characters. * File.open('t.txt') {|f| f.gets(1) } # => "F" @@ -15210,24 +15225,9 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * File.open('t.rus') {|f| f.gets(3).size } # => 2 * File.open('t.rus') {|f| f.gets(4).size } # => 2 * - * === Line Separator and Line Limit - * - * With arguments +sep+ and +limit+ given, - * combines the two behaviors: + * ===== Line Number * - * - Returns the next line as determined by line separator +sep+. - * - But returns no more bytes than are allowed by the limit. - * - * Example: - * - * File.open('t.txt') {|f| f.gets('li', 20) } # => "First li" - * File.open('t.txt') {|f| f.gets('li', 2) } # => "Fi" - * - * === Line Number - * - * A readable \IO stream has a non-negative integer line number. - * - * The relevant methods: + * A readable \IO stream has a non-negative integer line number: * * - IO#lineno: Returns the line number. * - IO#lineno=: Resets and returns the line number. @@ -15235,7 +15235,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * Unless modified by a call to method IO#lineno=, * the line number is the number of lines read * by certain line-oriented methods, - * according to the given line separator +sep+: + * according to the effective {line separator}[rdoc-ref:IO@Line+Separator]: * * - IO.foreach: Increments the line number on each call to the block. * - IO#each_line: Increments the line number on each call to the block. @@ -15325,6 +15325,12 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * $. # => 5 * f.close * + * === Line Output + * + * You can write to an \IO stream line-by-line using this method: + * + * - IO#puts: Writes objects to the stream. + * * == Character \IO * * You can process an \IO stream character-by-character using these methods: From 717adb564b4dd4a7e34b7b4b734795d7eb272c89 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 9 Feb 2024 09:12:45 -0800 Subject: [PATCH 104/142] YJIT: Fallback megamorphic opt_case_dispatch (#9894) --- yjit.rb | 5 +++-- yjit/src/codegen.rs | 8 +++++++- yjit/src/stats.rs | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/yjit.rb b/yjit.rb index a196275627e59e..13c394ff1e97b5 100644 --- a/yjit.rb +++ b/yjit.rb @@ -323,8 +323,9 @@ def _print_stats(out: $stderr) # :nodoc: out.puts "num_send_x86_rel32: " + format_number(13, stats[:num_send_x86_rel32]) out.puts "num_send_x86_reg: " + format_number(13, stats[:num_send_x86_reg]) end - out.puts "num_getivar_megamorphic: " + format_number(13, stats[:num_getivar_megamorphic]) - out.puts "num_setivar_megamorphic: " + format_number(13, stats[:num_setivar_megamorphic]) + out.puts "num_getivar_megamorphic: " + format_number(11, stats[:num_getivar_megamorphic]) + out.puts "num_setivar_megamorphic: " + format_number(11, stats[:num_setivar_megamorphic]) + out.puts "num_opt_case_megamorphic: " + format_number(10, stats[:num_opt_case_dispatch_megamorphic]) out.puts "num_throw: " + format_number(13, stats[:num_throw]) out.puts "num_throw_break: " + format_number_pct(13, stats[:num_throw_break], stats[:num_throw]) out.puts "num_throw_retry: " + format_number_pct(13, stats[:num_throw_retry], stats[:num_throw]) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index afcaf544063eba..1fc0e07fe26b8f 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4037,7 +4037,13 @@ fn gen_opt_case_dispatch( all_fixnum } - if comptime_key.fixnum_p() && comptime_key.0 <= u32::MAX.as_usize() && case_hash_all_fixnum_p(case_hash) { + // If megamorphic, fallback to compiling branch instructions after opt_case_dispatch + let megamorphic = asm.ctx.get_chain_depth() >= CASE_WHEN_MAX_DEPTH; + if megamorphic { + gen_counter_incr(asm, Counter::num_opt_case_dispatch_megamorphic); + } + + if comptime_key.fixnum_p() && comptime_key.0 <= u32::MAX.as_usize() && case_hash_all_fixnum_p(case_hash) && !megamorphic { if !assume_bop_not_redefined(jit, asm, ocb, INTEGER_REDEFINED_OP_FLAG, BOP_EQQ) { return None; } diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 056bb41931f14b..8214769d9b26d3 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -592,6 +592,7 @@ make_counters! { num_getivar_megamorphic, num_setivar_megamorphic, + num_opt_case_dispatch_megamorphic, num_throw, num_throw_break, From f635b4dd0e8a54ebd0aff7fbabd729fb4ad26606 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 13:39:53 +0000 Subject: [PATCH 105/142] [ruby/prism] RipperCompat: add array-refs, assigns, symbols, strings https://github.com/ruby/prism/commit/b771c7f2ec --- lib/prism/ripper_compat.rb | 80 ++++++++++++++++++++++++++++++++ test/prism/ripper_compat_test.rb | 32 +++++++++++++ 2 files changed, 112 insertions(+) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 6298fa8c0f1c60..5d488c0960fc04 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -120,6 +120,9 @@ def visit_array_node(node) # nodes -- unary and binary operators, "command" calls with # no parentheses, and call/fcall/vcall. def visit_call_node(node) + return visit_aref_node(node) if node.name == :[] + return visit_aref_field_node(node) if node.name == :[]= + if node.variable_call? raise NotImplementedError unless node.receiver.nil? @@ -153,6 +156,13 @@ def visit_call_node(node) end end + # Visit a LocalVariableWriteNode. + def visit_local_variable_write_node(node) + bounds(node.name_loc) + ident_val = on_ident(node.name.to_s) + on_assign(on_var_field(ident_val), visit(node.value)) + end + # Visit a LocalVariableAndWriteNode. def visit_local_variable_and_write_node(node) visit_binary_op_assign(node) @@ -299,6 +309,59 @@ def visit_rational_node(node) visit_number(node) { |text| on_rational(text) } end + # Visit a StringNode node. + def visit_string_node(node) + bounds(node.content_loc) + tstring_val = on_tstring_content(node.unescaped.to_s) + on_string_literal(on_string_add(on_string_content, tstring_val)) + end + + # Visit an XStringNode node. + def visit_x_string_node(node) + bounds(node.content_loc) + tstring_val = on_tstring_content(node.unescaped.to_s) + on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val)) + end + + # Visit an InterpolatedStringNode node. + def visit_interpolated_string_node(node) + parts = node.parts.map do |part| + case part + when StringNode + bounds(part.content_loc) + on_tstring_content(part.content) + when EmbeddedStatementsNode + on_string_embexpr(visit(part)) + else + raise NotImplementedError, "Unexpected node type in InterpolatedStringNode" + end + end + + string_list = parts.inject(on_string_content) do |items, item| + on_string_add(items, item) + end + + on_string_literal(string_list) + end + + # Visit an EmbeddedStatementsNode node. + def visit_embedded_statements_node(node) + visit(node.statements) + end + + # Visit a SymbolNode node. + def visit_symbol_node(node) + if node.opening && ['"', "'", "("].include?(node.opening[-1]) + bounds(node.value_loc) + tstring_val = on_tstring_content(node.value.to_s) + return on_dyna_symbol(on_string_add(on_string_content, tstring_val)) + end + + bounds(node.value_loc) + ident_val = on_ident(node.value.to_s) + on_symbol_literal(on_symbol(ident_val)) + end + # Visit a StatementsNode node. def visit_statements_node(node) bounds(node.location) @@ -406,6 +469,23 @@ def visit_binary_op_assign(node, operator: node.operator) on_opassign(on_var_field(ident_val), op_val, visit(node.value)) end + # In Prism this is a CallNode with :[] as the operator. + # In Ripper it's an :aref. + def visit_aref_node(node) + first_arg_val = visit(node.arguments.arguments[0]) + args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) + on_aref(visit(node.receiver), args_val) + end + + # In Prism this is a CallNode with :[]= as the operator. + # In Ripper it's an :aref_field. + def visit_aref_field_node(node) + first_arg_val = visit(node.arguments.arguments[0]) + args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) + assign_val = visit(node.arguments.arguments[1]) + on_assign(on_aref_field(visit(node.receiver), args_val), assign_val) + end + # Visit a node that represents a number. We need to explicitly handle the # unary - operator. def visit_number(node) diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb index c8fc208dff0ccf..40c609d58ca824 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_compat_test.rb @@ -110,6 +110,38 @@ def test_op_assign assert_equivalent("a /= b") end + def test_arrays + assert_equivalent("[1, 2, 7]") + assert_equivalent("[1, [2, 7]]") + end + + def test_array_refs + assert_equivalent("a[1]") + assert_equivalent("a[1] = 7") + end + + def test_strings + assert_equivalent("'a'") + assert_equivalent("'a\01'") + assert_equivalent("`a`") + assert_equivalent("`a\07`") + assert_equivalent('"a#{1}c"') + assert_equivalent('"a#{1}b#{2}c"') + assert_equivalent("`f\oo`") + end + + def test_symbols + assert_equivalent(":a") + assert_equivalent(":'a'") + assert_equivalent(':"a"') + assert_equivalent("%s(foo)") + end + + def test_assign + assert_equivalent("a = b") + assert_equivalent("a = 1") + end + private def assert_equivalent(source) From 86882565221cb709f4213e7c7699ba73f16f1107 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Thu, 8 Feb 2024 19:42:44 +0000 Subject: [PATCH 106/142] [ruby/prism] Update lib/prism/ripper_compat.rb https://github.com/ruby/prism/commit/2c53e017c1 Co-authored-by: Kevin Newton --- lib/prism/ripper_compat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb index 5d488c0960fc04..6e10737e0d5bd6 100644 --- a/lib/prism/ripper_compat.rb +++ b/lib/prism/ripper_compat.rb @@ -351,7 +351,7 @@ def visit_embedded_statements_node(node) # Visit a SymbolNode node. def visit_symbol_node(node) - if node.opening && ['"', "'", "("].include?(node.opening[-1]) + if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s")) bounds(node.value_loc) tstring_val = on_tstring_content(node.value.to_s) return on_dyna_symbol(on_string_add(on_string_content, tstring_val)) From e96c838ca40be75b57af289a182bb0ed6adaf829 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 9 Feb 2024 15:54:00 -0500 Subject: [PATCH 107/142] [PRISM] Fix flaky memory in scope nodes --- prism_compile.c | 13 +++++-------- test/.excludes-prism/TestSetTraceFunc.rb | 3 --- 2 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 test/.excludes-prism/TestSetTraceFunc.rb diff --git a/prism_compile.c b/prism_compile.c index b2e5c385870604..3bdd22ed3af6dc 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -2439,6 +2439,10 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous, const pm_parser_t *parser) { + // This is very important, otherwise the scope node could be seen as having + // certain flags set that _should not_ be set. + memset(scope, 0, sizeof(pm_scope_node_t)); + scope->base.type = PM_SCOPE_NODE; scope->base.location.start = node->location.start; scope->base.location.end = node->location.end; @@ -2446,17 +2450,10 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ scope->previous = previous; scope->parser = parser; scope->ast_node = (pm_node_t *)node; - scope->parameters = NULL; - scope->body = NULL; - scope->constants = NULL; - scope->local_table_for_iseq_size = 0; if (previous) { scope->constants = previous->constants; } - scope->index_lookup_table = NULL; - - pm_constant_id_list_init(&scope->locals); switch (PM_NODE_TYPE(node)) { case PM_BLOCK_NODE: { @@ -2541,7 +2538,7 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ default: assert(false && "unreachable"); break; - } + } } void diff --git a/test/.excludes-prism/TestSetTraceFunc.rb b/test/.excludes-prism/TestSetTraceFunc.rb deleted file mode 100644 index 3999fd3884d803..00000000000000 --- a/test/.excludes-prism/TestSetTraceFunc.rb +++ /dev/null @@ -1,3 +0,0 @@ -exclude(:test_tracepoint_nested_enabled_with_target, "unknown") -exclude(:test_allow_reentry, "unknown") -exclude(:test_tp_rescue, "unknown") From f7467e70e1803a230f9a0bf013c8134c1dde2c94 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 8 Feb 2024 15:46:53 -0500 Subject: [PATCH 108/142] Split line_no and node_id before new_insn_body Before this commit, there were many places where we had to generate dummy line nodes to hold both the line number and the node id that would then immediately get pulled out from the created node. Now we pass them explicitly so that we don't have to generate these nodes. This makes a clearer line between the parser and compiler, and also makes it easier to generate instructions when we don't have a specific node to tie them to. As such, it removes almost every single place where we needed to previously generate dummy nodes. This also makes it easier for the prism compiler, because now we can pass in line number and node id instead of trying to generate dummy nodes for every instruction that we compile. --- compile.c | 122 ++++++++++++++++++++++-------------------------- prism_compile.c | 4 +- 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/compile.c b/compile.c index 8ced4d8cc190d5..7459539d4504c3 100644 --- a/compile.c +++ b/compile.c @@ -222,30 +222,34 @@ const ID rb_iseq_shared_exc_local_tbl[] = {idERROR_INFO}; /* add an instruction */ #define ADD_INSN(seq, line_node, insn) \ - ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (line_node), BIN(insn), 0)) + ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, nd_line(line_node), nd_node_id(line_node), BIN(insn), 0)) + +/* add an instruction with the given line number and node id */ +#define ADD_SYNTHETIC_INSN(seq, line_no, node_id, insn) \ + ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (line_no), (node_id), BIN(insn), 0)) /* insert an instruction before next */ -#define INSERT_BEFORE_INSN(next, line_node, insn) \ - ELEM_INSERT_PREV(&(next)->link, (LINK_ELEMENT *) new_insn_body(iseq, (line_node), BIN(insn), 0)) +#define INSERT_BEFORE_INSN(next, line_no, node_id, insn) \ + ELEM_INSERT_PREV(&(next)->link, (LINK_ELEMENT *) new_insn_body(iseq, line_no, node_id, BIN(insn), 0)) /* insert an instruction after prev */ -#define INSERT_AFTER_INSN(prev, line_node, insn) \ - ELEM_INSERT_NEXT(&(prev)->link, (LINK_ELEMENT *) new_insn_body(iseq, (line_node), BIN(insn), 0)) +#define INSERT_AFTER_INSN(prev, line_no, node_id, insn) \ + ELEM_INSERT_NEXT(&(prev)->link, (LINK_ELEMENT *) new_insn_body(iseq, line_no, node_id, BIN(insn), 0)) /* add an instruction with some operands (1, 2, 3, 5) */ #define ADD_INSN1(seq, line_node, insn, op1) \ ADD_ELEM((seq), (LINK_ELEMENT *) \ - new_insn_body(iseq, (line_node), BIN(insn), 1, (VALUE)(op1))) + new_insn_body(iseq, nd_line(line_node), nd_node_id(line_node), BIN(insn), 1, (VALUE)(op1))) /* insert an instruction with some operands (1, 2, 3, 5) before next */ -#define INSERT_BEFORE_INSN1(next, line_node, insn, op1) \ +#define INSERT_BEFORE_INSN1(next, line_no, node_id, insn, op1) \ ELEM_INSERT_PREV(&(next)->link, (LINK_ELEMENT *) \ - new_insn_body(iseq, (line_node), BIN(insn), 1, (VALUE)(op1))) + new_insn_body(iseq, line_no, node_id, BIN(insn), 1, (VALUE)(op1))) /* insert an instruction with some operands (1, 2, 3, 5) after prev */ -#define INSERT_AFTER_INSN1(prev, line_node, insn, op1) \ +#define INSERT_AFTER_INSN1(prev, line_no, node_id, insn, op1) \ ELEM_INSERT_NEXT(&(prev)->link, (LINK_ELEMENT *) \ - new_insn_body(iseq, (line_node), BIN(insn), 1, (VALUE)(op1))) + new_insn_body(iseq, line_no, node_id, BIN(insn), 1, (VALUE)(op1))) #define LABEL_REF(label) ((label)->refcnt++) @@ -254,11 +258,11 @@ const ID rb_iseq_shared_exc_local_tbl[] = {idERROR_INFO}; #define ADD_INSN2(seq, line_node, insn, op1, op2) \ ADD_ELEM((seq), (LINK_ELEMENT *) \ - new_insn_body(iseq, (line_node), BIN(insn), 2, (VALUE)(op1), (VALUE)(op2))) + new_insn_body(iseq, nd_line(line_node), nd_node_id(line_node), BIN(insn), 2, (VALUE)(op1), (VALUE)(op2))) #define ADD_INSN3(seq, line_node, insn, op1, op2, op3) \ ADD_ELEM((seq), (LINK_ELEMENT *) \ - new_insn_body(iseq, (line_node), BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3))) + new_insn_body(iseq, nd_line(line_node), nd_node_id(line_node), BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3))) /* Specific Insn factory */ #define ADD_SEND(seq, line_node, id, argc) \ @@ -280,7 +284,7 @@ const ID rb_iseq_shared_exc_local_tbl[] = {idERROR_INFO}; ADD_SEND_R((seq), (line_node), (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL), NULL) #define ADD_SEND_R(seq, line_node, id, argc, block, flag, keywords) \ - ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, (line_node), (id), (VALUE)(argc), (block), (VALUE)(flag), (keywords))) + ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, nd_line(line_node), nd_node_id(line_node), (id), (VALUE)(argc), (block), (VALUE)(flag), (keywords))) #define ADD_TRACE(seq, event) \ ADD_ELEM((seq), (LINK_ELEMENT *)new_trace_body(iseq, (event), 0)) @@ -473,7 +477,7 @@ static void dump_disasm_list(const LINK_ELEMENT *elem); static int insn_data_length(INSN *iobj); static int calc_sp_depth(int depth, INSN *iobj); -static INSN *new_insn_body(rb_iseq_t *iseq, const NODE *const line_node, enum ruby_vminsn_type insn_id, int argc, ...); +static INSN *new_insn_body(rb_iseq_t *iseq, int line_no, int node_id, enum ruby_vminsn_type insn_id, int argc, ...); static LABEL *new_label_body(rb_iseq_t *iseq, long line); static ADJUST *new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line); static TRACE *new_trace_body(rb_iseq_t *iseq, rb_event_flag_t event, long data); @@ -694,9 +698,7 @@ add_trace_branch_coverage(rb_iseq_t *iseq, LINK_ANCHOR *const seq, const NODE *n } ADD_TRACE_WITH_DATA(seq, RUBY_EVENT_COVERAGE_BRANCH, counter_idx); - - NODE dummy_line_node = generate_dummy_line_node(last_lineno, nd_node_id(node)); - ADD_INSN(seq, &dummy_line_node, nop); + ADD_SYNTHETIC_INSN(seq, last_lineno, nd_node_id(node), nop); } #define ISEQ_LAST_LINE(iseq) (ISEQ_COMPILE_DATA(iseq)->last_line) @@ -854,8 +856,7 @@ rb_iseq_compile_callback(rb_iseq_t *iseq, const struct rb_iseq_new_with_callback (*ifunc->func)(iseq, ret, ifunc->data); - NODE dummy_line_node = generate_dummy_line_node(ISEQ_COMPILE_DATA(iseq)->last_line, -1); - ADD_INSN(ret, &dummy_line_node, leave); + ADD_SYNTHETIC_INSN(ret, ISEQ_COMPILE_DATA(iseq)->last_line, -1, leave); CHECK(iseq_setup_insn(iseq, ret)); return iseq_setup(iseq, ret); @@ -891,8 +892,7 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) end->rescued = LABEL_RESCUE_END; ADD_TRACE(ret, RUBY_EVENT_B_CALL); - NODE dummy_line_node = generate_dummy_line_node(ISEQ_BODY(iseq)->location.first_lineno, -1); - ADD_INSN (ret, &dummy_line_node, nop); + ADD_SYNTHETIC_INSN(ret, ISEQ_BODY(iseq)->location.first_lineno, -1, nop); ADD_LABEL(ret, start); CHECK(COMPILE(ret, "block body", RNODE_SCOPE(node)->nd_body)); ADD_LABEL(ret, end); @@ -966,8 +966,7 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) ADD_INSN1(ret, &dummy_line_node, throw, INT2FIX(0) /* continue throw */ ); } else { - NODE dummy_line_node = generate_dummy_line_node(ISEQ_COMPILE_DATA(iseq)->last_line, -1); - ADD_INSN(ret, &dummy_line_node, leave); + ADD_SYNTHETIC_INSN(ret, ISEQ_COMPILE_DATA(iseq)->last_line, -1, leave); } #if OPT_SUPPORT_JOKE @@ -1400,8 +1399,7 @@ iseq_insn_each_object_write_barrier(VALUE obj, VALUE iseq) } static INSN * -new_insn_core(rb_iseq_t *iseq, const NODE *line_node, - int insn_id, int argc, VALUE *argv) +new_insn_core(rb_iseq_t *iseq, int line_no, int node_id, int insn_id, int argc, VALUE *argv) { INSN *iobj = compile_data_alloc_insn(iseq); @@ -1410,8 +1408,8 @@ new_insn_core(rb_iseq_t *iseq, const NODE *line_node, iobj->link.type = ISEQ_ELEMENT_INSN; iobj->link.next = 0; iobj->insn_id = insn_id; - iobj->insn_info.line_no = nd_line(line_node); - iobj->insn_info.node_id = nd_node_id(line_node); + iobj->insn_info.line_no = line_no; + iobj->insn_info.node_id = node_id; iobj->insn_info.events = 0; iobj->operands = argv; iobj->operand_size = argc; @@ -1423,7 +1421,7 @@ new_insn_core(rb_iseq_t *iseq, const NODE *line_node, } static INSN * -new_insn_body(rb_iseq_t *iseq, const NODE *const line_node, enum ruby_vminsn_type insn_id, int argc, ...) +new_insn_body(rb_iseq_t *iseq, int line_no, int node_id, enum ruby_vminsn_type insn_id, int argc, ...) { VALUE *operands = 0; va_list argv; @@ -1437,7 +1435,7 @@ new_insn_body(rb_iseq_t *iseq, const NODE *const line_node, enum ruby_vminsn_typ } va_end(argv); } - return new_insn_core(iseq, line_node, insn_id, argc, operands); + return new_insn_core(iseq, line_no, node_id, insn_id, argc, operands); } static const struct rb_callinfo * @@ -1462,7 +1460,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, unsigned int flag, struct rb_cal } static INSN * -new_insn_send(rb_iseq_t *iseq, const NODE *const line_node, ID id, VALUE argc, const rb_iseq_t *blockiseq, VALUE flag, struct rb_callinfo_kwarg *keywords) +new_insn_send(rb_iseq_t *iseq, int line_no, int node_id, ID id, VALUE argc, const rb_iseq_t *blockiseq, VALUE flag, struct rb_callinfo_kwarg *keywords) { VALUE *operands = compile_data_calloc2(iseq, sizeof(VALUE), 2); VALUE ci = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), FIX2INT(flag), keywords, blockiseq != NULL); @@ -1471,7 +1469,7 @@ new_insn_send(rb_iseq_t *iseq, const NODE *const line_node, ID id, VALUE argc, c if (blockiseq) { RB_OBJ_WRITTEN(iseq, Qundef, blockiseq); } - INSN *insn = new_insn_core(iseq, line_node, BIN(send), 2, operands); + INSN *insn = new_insn_core(iseq, line_no, node_id, BIN(send), 2, operands); RB_OBJ_WRITTEN(iseq, Qundef, ci); RB_GC_GUARD(ci); return insn; @@ -1590,8 +1588,7 @@ iseq_insert_nop_between_end_and_cont(rb_iseq_t *iseq) for (e = end; e && (IS_LABEL(e) || IS_TRACE(e)); e = e->next) { if (e == cont) { - NODE dummy_line_node = generate_dummy_line_node(0, -1); - INSN *nop = new_insn_core(iseq, &dummy_line_node, BIN(nop), 0, 0); + INSN *nop = new_insn_core(iseq, 0, -1, BIN(nop), 0, 0); ELEM_INSERT_NEXT(end, &nop->link); break; } @@ -3148,7 +3145,6 @@ optimize_checktype(rb_iseq_t *iseq, INSN *iobj) } line = ciobj->insn_info.line_no; node_id = ciobj->insn_info.node_id; - NODE dummy_line_node = generate_dummy_line_node(line, node_id); if (!dest) { if (niobj->link.next && IS_LABEL(niobj->link.next)) { dest = (LABEL *)niobj->link.next; /* reuse label */ @@ -3158,9 +3154,9 @@ optimize_checktype(rb_iseq_t *iseq, INSN *iobj) ELEM_INSERT_NEXT(&niobj->link, &dest->link); } } - INSERT_AFTER_INSN1(iobj, &dummy_line_node, jump, dest); + INSERT_AFTER_INSN1(iobj, line, node_id, jump, dest); LABEL_REF(dest); - if (!dup) INSERT_AFTER_INSN(iobj, &dummy_line_node, pop); + if (!dup) INSERT_AFTER_INSN(iobj, line, node_id, pop); return TRUE; } @@ -3318,8 +3314,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * pop * jump L1 */ - NODE dummy_line_node = generate_dummy_line_node(iobj->insn_info.line_no, iobj->insn_info.node_id); - INSN *popiobj = new_insn_core(iseq, &dummy_line_node, BIN(pop), 0, 0); + INSN *popiobj = new_insn_core(iseq, iobj->insn_info.line_no, iobj->insn_info.node_id, BIN(pop), 0, 0); ELEM_REPLACE(&piobj->link, &popiobj->link); } } @@ -3498,14 +3493,12 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal ELEM_REMOVE(iobj->link.prev); } else if (!iseq_pop_newarray(iseq, pobj)) { - NODE dummy_line_node = generate_dummy_line_node(pobj->insn_info.line_no, pobj->insn_info.node_id); - pobj = new_insn_core(iseq, &dummy_line_node, BIN(pop), 0, NULL); + pobj = new_insn_core(iseq, pobj->insn_info.line_no, pobj->insn_info.node_id, BIN(pop), 0, NULL); ELEM_INSERT_PREV(&iobj->link, &pobj->link); } if (cond) { if (prev_dup) { - NODE dummy_line_node = generate_dummy_line_node(pobj->insn_info.line_no, pobj->insn_info.node_id); - pobj = new_insn_core(iseq, &dummy_line_node, BIN(putnil), 0, NULL); + pobj = new_insn_core(iseq, pobj->insn_info.line_no, pobj->insn_info.node_id, BIN(putnil), 0, NULL); ELEM_INSERT_NEXT(&iobj->link, &pobj->link); } iobj->insn_id = BIN(jump); @@ -3551,8 +3544,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } else if (previ == BIN(concatarray)) { INSN *piobj = (INSN *)prev; - NODE dummy_line_node = generate_dummy_line_node(piobj->insn_info.line_no, piobj->insn_info.node_id); - INSERT_BEFORE_INSN1(piobj, &dummy_line_node, splatarray, Qfalse); + INSERT_BEFORE_INSN1(piobj, piobj->insn_info.line_no, piobj->insn_info.node_id, splatarray, Qfalse); INSN_OF(piobj) = BIN(pop); } else if (previ == BIN(concatstrings)) { @@ -3617,7 +3609,6 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } } else { - NODE dummy_line_node = generate_dummy_line_node(iobj->insn_info.line_no, iobj->insn_info.node_id); long diff = FIX2LONG(op1) - FIX2LONG(op2); INSN_OF(iobj) = BIN(opt_reverse); OPERAND_AT(iobj, 0) = OPERAND_AT(next, 0); @@ -3631,7 +3622,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * opt_reverse Y */ for (; diff > 0; diff--) { - INSERT_BEFORE_INSN(iobj, &dummy_line_node, pop); + INSERT_BEFORE_INSN(iobj, iobj->insn_info.line_no, iobj->insn_info.node_id, pop); } } else { /* (op1 < op2) */ @@ -3643,7 +3634,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * opt_reverse Y */ for (; diff < 0; diff++) { - INSERT_BEFORE_INSN(iobj, &dummy_line_node, putnil); + INSERT_BEFORE_INSN(iobj, iobj->insn_info.line_no, iobj->insn_info.node_id, putnil); } } } @@ -4222,8 +4213,7 @@ new_unified_insn(rb_iseq_t *iseq, list = list->next; } - NODE dummy_line_node = generate_dummy_line_node(iobj->insn_info.line_no, iobj->insn_info.node_id); - return new_insn_core(iseq, &dummy_line_node, insn_id, argc, operands); + return new_insn_core(iseq, iobj->insn_info.line_no, iobj->insn_info.node_id, insn_id, argc, operands); } #endif @@ -5399,18 +5389,22 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const } OPERAND_AT(iobj, 0) = (VALUE)ci; RB_OBJ_WRITTEN(iseq, Qundef, iobj); + /* Given: h[*a], h[*b, 1] = ary * h[*a] uses splatarray false and does not set VM_CALL_ARGS_SPLAT_MUT, * so this uses splatarray true on a to dup it before using pushtoarray * h[*b, 1] uses splatarray true and sets VM_CALL_ARGS_SPLAT_MUT, * so you can use pushtoarray directly */ + int line_no = nd_line(line_node); + int node_id = nd_node_id(line_node); + if (dupsplat) { - INSERT_BEFORE_INSN(iobj, line_node, swap); - INSERT_BEFORE_INSN1(iobj, line_node, splatarray, Qtrue); - INSERT_BEFORE_INSN(iobj, line_node, swap); + INSERT_BEFORE_INSN(iobj, line_no, node_id, swap); + INSERT_BEFORE_INSN1(iobj, line_no, node_id, splatarray, Qtrue); + INSERT_BEFORE_INSN(iobj, line_no, node_id, swap); } - INSERT_BEFORE_INSN1(iobj, line_node, pushtoarray, INT2FIX(1)); + INSERT_BEFORE_INSN1(iobj, line_no, node_id, pushtoarray, INT2FIX(1)); } ADD_INSN(lhs, line_node, pop); if (argc != 1) { @@ -5629,7 +5623,7 @@ compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, while (memo) { VALUE topn_arg = INT2FIX((state.num_args - memo->argn) + memo->lhs_pos); for (int i = 0; i < memo->num_args; i++) { - INSERT_BEFORE_INSN1(memo->before_insn, memo->line_node, topn, topn_arg); + INSERT_BEFORE_INSN1(memo->before_insn, nd_line(memo->line_node), nd_node_id(memo->line_node), topn, topn_arg); } tmp_memo = memo->next; free(memo); @@ -5955,8 +5949,7 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, static void build_defined_rescue_iseq(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const void *unused) { - NODE dummy_line_node = generate_dummy_line_node(0, -1); - ADD_INSN(ret, &dummy_line_node, putnil); + ADD_SYNTHETIC_INSN(ret, 0, -1, putnil); iseq_set_exception_local_table(iseq); } @@ -6002,7 +5995,7 @@ compile_defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const lfinish[2] = 0; defined_expr(iseq, ret, RNODE_DEFINED(node)->nd_head, lfinish, needstr); if (lfinish[1]) { - ELEM_INSERT_NEXT(last, &new_insn_body(iseq, line_node, BIN(putnil), 0)->link); + ELEM_INSERT_NEXT(last, &new_insn_body(iseq, nd_line(line_node), nd_node_id(line_node), BIN(putnil), 0)->link); ADD_INSN(ret, line_node, swap); if (lfinish[2]) { ADD_LABEL(ret, lfinish[2]); @@ -6324,7 +6317,7 @@ compile_named_capture_assign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE last = ret->last; NO_CHECK(COMPILE_POPPED(ret, "capture", RNODE_BLOCK(vars)->nd_head)); last = last->next; /* putobject :var */ - cap = new_insn_send(iseq, line_node, idAREF, INT2FIX(1), + cap = new_insn_send(iseq, nd_line(line_node), nd_node_id(line_node), idAREF, INT2FIX(1), NULL, INT2FIX(0), NULL); ELEM_INSERT_PREV(last->next, (LINK_ELEMENT *)cap); #if !defined(NAMED_CAPTURE_SINGLE_OPT) || NAMED_CAPTURE_SINGLE_OPT-0 @@ -8248,9 +8241,7 @@ compile_resbody(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, if (nd_type(RNODE_RESBODY(resq)->nd_body) == NODE_BEGIN && RNODE_BEGIN(RNODE_RESBODY(resq)->nd_body)->nd_body == NULL) { // empty body - int lineno = nd_line(RNODE_RESBODY(resq)->nd_body); - NODE dummy_line_node = generate_dummy_line_node(lineno, -1); - ADD_INSN(ret, &dummy_line_node, putnil); + ADD_SYNTHETIC_INSN(ret, nd_line(RNODE_RESBODY(resq)->nd_body), -1, putnil); } else { CHECK(COMPILE(ret, "resbody body", RNODE_RESBODY(resq)->nd_body)); @@ -9929,8 +9920,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, const NODE *node, int poppe int lineno = ISEQ_COMPILE_DATA(iseq)->last_line; if (lineno == 0) lineno = FIX2INT(rb_iseq_first_lineno(iseq)); debugs("node: NODE_NIL(implicit)\n"); - NODE dummy_line_node = generate_dummy_line_node(lineno, -1); - ADD_INSN(ret, &dummy_line_node, putnil); + ADD_SYNTHETIC_INSN(ret, lineno, -1, putnil); } return COMPILE_OK; } @@ -11143,9 +11133,8 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, argv = compile_data_calloc2(iseq, sizeof(VALUE), argc); // add element before operand setup to make GC root - NODE dummy_line_node = generate_dummy_line_node(line_no, node_id); ADD_ELEM(anchor, - (LINK_ELEMENT*)new_insn_core(iseq, &dummy_line_node, + (LINK_ELEMENT*)new_insn_core(iseq, line_no, node_id, (enum ruby_vminsn_type)insn_id, argc, argv)); for (j=0; jlink); + ELEM_INSERT_NEXT(last, &new_insn_body(iseq, nd_line(&dummy_line_node), nd_node_id(&dummy_line_node), BIN(putnil), 0)->link); PM_SWAP; if (lfinish[2]) { ADD_LABEL(ret, lfinish[2]); From e7b0a01002323d9ed8eed9abca2a4979ebe9ae32 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 9 Feb 2024 14:12:24 -0800 Subject: [PATCH 109/142] YJIT: Add top ISEQ call counts to --yjit-stats (#9906) --- yjit.c | 24 +++++++ yjit.rb | 32 ++++------ yjit/bindgen/src/main.rs | 2 + yjit/src/codegen.rs | 108 ++++++++++++++++---------------- yjit/src/cruby_bindings.inc.rs | 2 + yjit/src/stats.rs | 110 +++++++++++++++++++-------------- 6 files changed, 161 insertions(+), 117 deletions(-) diff --git a/yjit.c b/yjit.c index cf6d81092fae33..4e9fed35ae827c 100644 --- a/yjit.c +++ b/yjit.c @@ -895,6 +895,30 @@ rb_yjit_dump_iseq_loc(const rb_iseq_t *iseq, uint32_t insn_idx) fprintf(stderr, "%s %.*s:%u\n", __func__, (int)len, ptr, rb_iseq_line_no(iseq, insn_idx)); } +// Get the number of digits required to print an integer +static int +num_digits(int integer) +{ + int num = 1; + while (integer /= 10) { + num++; + } + return num; +} + +// Allocate a C string that formats an ISEQ label like iseq_inspect() +char * +rb_yjit_iseq_inspect(const rb_iseq_t *iseq) +{ + const char *label = RSTRING_PTR(iseq->body->location.label); + const char *path = RSTRING_PTR(rb_iseq_path(iseq)); + int lineno = iseq->body->location.code_location.beg_pos.lineno; + + char *buf = ZALLOC_N(char, strlen(label) + strlen(path) + num_digits(lineno) + 3); + sprintf(buf, "%s@%s:%d", label, path, lineno); + return buf; +} + // The FL_TEST() macro VALUE rb_FL_TEST(VALUE obj, VALUE flags) diff --git a/yjit.rb b/yjit.rb index 13c394ff1e97b5..0a649e0dc71ddc 100644 --- a/yjit.rb +++ b/yjit.rb @@ -315,10 +315,11 @@ def _print_stats(out: $stderr) # :nodoc: out.puts "num_send_polymorphic: " + format_number_pct(13, stats[:num_send_polymorphic], stats[:num_send]) out.puts "num_send_megamorphic: " + format_number_pct(13, stats[:send_megamorphic], stats[:num_send]) out.puts "num_send_dynamic: " + format_number_pct(13, stats[:num_send_dynamic], stats[:num_send]) - out.puts "num_send_inline: " + format_number_pct(13, stats[:num_send_inline], stats[:num_send]) - out.puts "num_send_leaf_builtin: " + format_number_pct(13, stats[:num_send_leaf_builtin], stats[:num_send]) out.puts "num_send_cfunc: " + format_number_pct(13, stats[:num_send_cfunc], stats[:num_send]) out.puts "num_send_cfunc_inline: " + format_number_pct(13, stats[:num_send_cfunc_inline], stats[:num_send_cfunc]) + out.puts "num_send_iseq: " + format_number_pct(13, stats[:num_send_iseq], stats[:num_send]) + out.puts "num_send_iseq_leaf: " + format_number_pct(13, stats[:num_send_iseq_leaf], stats[:num_send_iseq]) + out.puts "num_send_iseq_inline: " + format_number_pct(13, stats[:num_send_iseq_inline], stats[:num_send_iseq]) if stats[:num_send_x86_rel32] != 0 || stats[:num_send_x86_reg] != 0 out.puts "num_send_x86_rel32: " + format_number(13, stats[:num_send_x86_rel32]) out.puts "num_send_x86_reg: " + format_number(13, stats[:num_send_x86_reg]) @@ -385,17 +386,12 @@ def _print_stats(out: $stderr) # :nodoc: print_sorted_exit_counts(stats, out: out, prefix: "exit_") - print_sorted_cfunc_calls(stats, out:out) + print_sorted_method_calls(stats[:cfunc_calls], stats[:num_send_cfunc], out: out, type: 'C') + print_sorted_method_calls(stats[:iseq_calls], stats[:num_send_iseq], out: out, type: 'ISEQ') end - def print_sorted_cfunc_calls(stats, out:, how_many: 20, left_pad: 4) # :nodoc: - calls = stats[:cfunc_calls] - if calls.empty? - return - end - - # Total number of cfunc calls - num_send_cfunc = stats[:num_send_cfunc] + def print_sorted_method_calls(calls, num_calls, out:, type:, how_many: 20, left_pad: 4) # :nodoc: + return if calls.empty? # Sort calls by decreasing frequency and keep the top N pairs = calls.map { |k,v| [k, v] } @@ -404,16 +400,14 @@ def print_sorted_cfunc_calls(stats, out:, how_many: 20, left_pad: 4) # :nodoc: pairs = pairs[0...how_many] top_n_total = pairs.sum { |name, count| count } - top_n_pct = 100.0 * top_n_total / num_send_cfunc + top_n_pct = 100.0 * top_n_total / num_calls longest_name_len = pairs.max_by { |name, count| name.length }.first.length - out.puts "Top-#{pairs.size} most frequent C calls (#{"%.1f" % top_n_pct}% of C calls):" + out.puts "Top-#{pairs.size} most frequent #{type} calls (#{"%.1f" % top_n_pct}% of #{type} calls):" pairs.each do |name, count| - padding = longest_name_len + left_pad - padded_name = "%#{padding}s" % name - padded_count = format_number_pct(10, count, num_send_cfunc) - out.puts("#{padded_name}: #{padded_count}") + padded_count = format_number_pct(10, count, num_calls) + out.puts("#{padded_count}: #{name}") end end @@ -437,10 +431,8 @@ def print_sorted_exit_counts(stats, out:, prefix:, how_many: 20, left_pad: 4) # longest_insn_name_len = exits.max_by { |name, count| name.length }.first.length exits.each do |name, count| - padding = longest_insn_name_len + left_pad - padded_name = "%#{padding}s" % name padded_count = format_number_pct(10, count, total_exits) - out.puts("#{padded_name}: #{padded_count}") + out.puts("#{padded_count}: #{name}") end else out.puts "total_exits: " + format_number(10, total_exits) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index a09ab068cb935b..899298dd2eeace 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -308,6 +308,7 @@ fn main() { .allowlist_function("rb_yjit_mark_unused") .allowlist_function("rb_yjit_get_page_size") .allowlist_function("rb_yjit_iseq_builtin_attrs") + .allowlist_function("rb_yjit_iseq_inspect") .allowlist_function("rb_yjit_builtin_function") .allowlist_function("rb_set_cfp_(pc|sp)") .allowlist_function("rb_yjit_multi_ractor_p") @@ -377,6 +378,7 @@ fn main() { // From gc.h and internal/gc.h .allowlist_function("rb_class_allocate_instance") .allowlist_function("rb_obj_info") + .allowlist_function("ruby_xfree") // From include/ruby/debug.h .allowlist_function("rb_profile_frames") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 1fc0e07fe26b8f..322553cce13e98 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -16,6 +16,7 @@ use std::cell::Cell; use std::cmp; use std::cmp::min; use std::collections::HashMap; +use std::ffi::c_void; use std::ffi::CStr; use std::mem; use std::os::raw::c_int; @@ -5966,32 +5967,6 @@ fn gen_send_cfunc( } } - // Log the name of the method we're calling to, - // note that we intentionally don't do this for inlined cfuncs - if get_option!(gen_stats) { - // TODO: extract code to get method name string into its own function - - // Assemble the method name string - let mid = unsafe { vm_ci_mid(ci) }; - let class_name = if let Some(class) = recv_known_class { - unsafe { cstr_to_rust_string(rb_class2name(class)) }.unwrap() - } else { - "Unknown".to_string() - }; - let method_name = if mid != 0 { - unsafe { cstr_to_rust_string(rb_id2name(mid)) }.unwrap() - } else { - "Unknown".to_string() - }; - let name_str = format!("{}#{}", class_name, method_name); - - // Get an index for this cfunc name - let cfunc_idx = get_cfunc_idx(&name_str); - - // Increment the counter for this cfunc - asm.ccall(incr_cfunc_counter as *const u8, vec![cfunc_idx.into()]); - } - // Check for interrupts gen_check_ints(asm, Counter::guard_send_interrupted); @@ -6180,6 +6155,20 @@ fn gen_send_cfunc( let stack_ret = asm.stack_push(Type::Unknown); asm.mov(stack_ret, ret); + // Log the name of the method we're calling to. We intentionally don't do this for inlined cfuncs. + // We also do this after the C call to minimize the impact of spill_temps() on asm.ccall(). + if get_option!(gen_stats) { + // Assemble the method name string + let mid = unsafe { vm_ci_mid(ci) }; + let name_str = get_method_name(recv_known_class, mid); + + // Get an index for this cfunc name + let cfunc_idx = get_cfunc_idx(&name_str); + + // Increment the counter for this cfunc + asm.ccall(incr_cfunc_counter as *const u8, vec![cfunc_idx.into()]); + } + // Pop the stack frame (ec->cfp++) // Instead of recalculating, we can reuse the previous CFP, which is stored in a callee-saved // register @@ -6650,6 +6639,9 @@ fn gen_send_iseq( } } + // Increment total ISEQ send count + gen_counter_incr(asm, Counter::num_send_iseq); + // Shortcut for special `Primitive.attr! :leaf` builtins let builtin_attrs = unsafe { rb_yjit_iseq_builtin_attrs(iseq) }; let builtin_func_raw = unsafe { rb_yjit_builtin_function(iseq) }; @@ -6671,7 +6663,7 @@ fn gen_send_iseq( } asm_comment!(asm, "inlined leaf builtin"); - gen_counter_incr(asm, Counter::num_send_leaf_builtin); + gen_counter_incr(asm, Counter::num_send_iseq_leaf); // The callee may allocate, e.g. Integer#abs on a Bignum. // Save SP for GC, save PC for allocation tracing, and prepare @@ -6705,7 +6697,7 @@ fn gen_send_iseq( // Inline simple ISEQs whose return value is known at compile time if let (Some(value), None, false) = (iseq_get_return_value(iseq), block_arg_type, opt_send_call) { asm_comment!(asm, "inlined simple ISEQ"); - gen_counter_incr(asm, Counter::num_send_inline); + gen_counter_incr(asm, Counter::num_send_iseq_inline); // Pop receiver and arguments asm.stack_pop(argc as usize + if captured_opnd.is_some() { 0 } else { 1 }); @@ -7210,6 +7202,19 @@ fn gen_send_iseq( pc: None, // We are calling into jitted code, which will set the PC as necessary }); + // Log the name of the method we're calling to. We intentionally don't do this for inlined ISEQs. + // We also do this after gen_push_frame() to minimize the impact of spill_temps() on asm.ccall(). + if get_option!(gen_stats) { + // Assemble the ISEQ name string + let name_str = get_iseq_name(iseq); + + // Get an index for this ISEQ name + let iseq_idx = get_iseq_idx(&name_str); + + // Increment the counter for this cfunc + asm.ccall(incr_iseq_counter as *const u8, vec![iseq_idx.into()]); + } + // No need to set cfp->pc since the callee sets it whenever calling into routines // that could look at it through jit_save_pc(). // mov(cb, REG0, const_ptr_opnd(start_pc)); @@ -7639,16 +7644,7 @@ fn gen_send_general( // Log the name of the method we're calling to #[cfg(feature = "disasm")] - { - let class_name = unsafe { cstr_to_rust_string(rb_class2name(comptime_recv_klass)) }; - let method_name = unsafe { cstr_to_rust_string(rb_id2name(mid)) }; - match (class_name, method_name) { - (Some(class_name), Some(method_name)) => { - asm_comment!(asm, "call to {}#{}", class_name, method_name); - } - _ => {} - } - } + asm_comment!(asm, "call to {}", get_method_name(Some(comptime_recv_klass), mid)); // Gather some statistics about sends gen_counter_incr(asm, Counter::num_send); @@ -8069,6 +8065,27 @@ fn gen_send_general( } } +/// Assemble "{class_name}#{method_name}" from a class pointer and a method ID +fn get_method_name(class: Option, mid: u64) -> String { + let class_name = class.and_then(|class| unsafe { + cstr_to_rust_string(rb_class2name(class)) + }).unwrap_or_else(|| "Unknown".to_string()); + let method_name = if mid != 0 { + unsafe { cstr_to_rust_string(rb_id2name(mid)) } + } else { + None + }.unwrap_or_else(|| "Unknown".to_string()); + format!("{}#{}", class_name, method_name) +} + +/// Assemble "{label}@{iseq_path}:{lineno}" (iseq_inspect() format) from an ISEQ +fn get_iseq_name(iseq: IseqPtr) -> String { + let c_string = unsafe { rb_yjit_iseq_inspect(iseq) }; + let string = unsafe { CStr::from_ptr(c_string) }.to_str() + .unwrap_or_else(|_| "not UTF-8").to_string(); + unsafe { ruby_xfree(c_string as *mut c_void); } + string +} /// Shifts the stack for send in order to remove the name of the method /// Comment below borrow from vm_call_opt_send in vm_insnhelper.c @@ -8239,20 +8256,7 @@ fn gen_invokeblock_specialized( Counter::guard_invokeblock_iseq_block_changed, ); - gen_send_iseq( - jit, - asm, - ocb, - comptime_iseq, - ci, - VM_FRAME_MAGIC_BLOCK, - None, - 0 as _, - None, - flags, - argc, - Some(captured_opnd), - ) + gen_send_iseq(jit, asm, ocb, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd)) } else if comptime_handler.0 & 0x3 == 0x3 { // VM_BH_IFUNC_P // We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet. if flags & VM_CALL_ARGS_SPLAT != 0 { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 4246468cea0192..dabd3c968122e1 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -907,6 +907,7 @@ pub const RUBY_OFFSET_RSTRING_LEN: rstring_offsets = 16; pub type rstring_offsets = u32; pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; extern "C" { + pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; pub fn rb_singleton_class(obj: VALUE) -> VALUE; pub fn rb_get_alloc_func(klass: VALUE) -> rb_alloc_func_t; @@ -1154,6 +1155,7 @@ extern "C" { pub fn rb_yjit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yjit_ruby2_keywords_splat_p(obj: VALUE) -> usize; pub fn rb_yjit_dump_iseq_loc(iseq: *const rb_iseq_t, insn_idx: u32); + pub fn rb_yjit_iseq_inspect(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_char; pub fn rb_FL_TEST(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_FL_TEST_RAW(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_RB_TYPE_P(obj: VALUE, t: ruby_value_type) -> bool; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 8214769d9b26d3..395a1b45f9698b 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -53,56 +53,64 @@ unsafe impl GlobalAlloc for StatsAlloc { } } -/// Mapping of C function name to integer indices +/// Mapping of C function / ISEQ name to integer indices /// This is accessed at compilation time only (protected by a lock) static mut CFUNC_NAME_TO_IDX: Option> = None; +static mut ISEQ_NAME_TO_IDX: Option> = None; -/// Vector of call counts for each C function index +/// Vector of call counts for each C function / ISEQ index /// This is modified (but not resized) by JITted code static mut CFUNC_CALL_COUNT: Option> = None; +static mut ISEQ_CALL_COUNT: Option> = None; /// Assign an index to a given cfunc name string -pub fn get_cfunc_idx(name: &str) -> usize -{ - //println!("{}", name); - - unsafe { - if CFUNC_NAME_TO_IDX.is_none() { - CFUNC_NAME_TO_IDX = Some(HashMap::default()); - } +pub fn get_cfunc_idx(name: &str) -> usize { + unsafe { get_method_idx(name, &mut CFUNC_NAME_TO_IDX, &mut CFUNC_CALL_COUNT) } +} - if CFUNC_CALL_COUNT.is_none() { - CFUNC_CALL_COUNT = Some(Vec::default()); - } +/// Assign an index to a given ISEQ name string +pub fn get_iseq_idx(name: &str) -> usize { + unsafe { get_method_idx(name, &mut ISEQ_NAME_TO_IDX, &mut ISEQ_CALL_COUNT) } +} - let name_to_idx = CFUNC_NAME_TO_IDX.as_mut().unwrap(); +fn get_method_idx( + name: &str, + method_name_to_idx: &mut Option>, + method_call_count: &mut Option>, +) -> usize { + //println!("{}", name); - match name_to_idx.get(name) { - Some(idx) => *idx, - None => { - let idx = name_to_idx.len(); - name_to_idx.insert(name.to_string(), idx); + let name_to_idx = method_name_to_idx.get_or_insert_with(HashMap::default); + let call_count = method_call_count.get_or_insert_with(Vec::default); - // Resize the call count vector - let cfunc_call_count = CFUNC_CALL_COUNT.as_mut().unwrap(); - if idx >= cfunc_call_count.len() { - cfunc_call_count.resize(idx + 1, 0); - } + match name_to_idx.get(name) { + Some(idx) => *idx, + None => { + let idx = name_to_idx.len(); + name_to_idx.insert(name.to_string(), idx); - idx + // Resize the call count vector + if idx >= call_count.len() { + call_count.resize(idx + 1, 0); } + + idx } } } // Increment the counter for a C function -pub extern "C" fn incr_cfunc_counter(idx: usize) -{ - unsafe { - let cfunc_call_count = CFUNC_CALL_COUNT.as_mut().unwrap(); - assert!(idx < cfunc_call_count.len()); - cfunc_call_count[idx] += 1; - } +pub extern "C" fn incr_cfunc_counter(idx: usize) { + let cfunc_call_count = unsafe { CFUNC_CALL_COUNT.as_mut().unwrap() }; + assert!(idx < cfunc_call_count.len()); + cfunc_call_count[idx] += 1; +} + +// Increment the counter for an ISEQ +pub extern "C" fn incr_iseq_counter(idx: usize) { + let iseq_call_count = unsafe { ISEQ_CALL_COUNT.as_mut().unwrap() }; + assert!(idx < iseq_call_count.len()); + iseq_call_count[idx] += 1; } // YJIT exit counts for each instruction type @@ -585,10 +593,11 @@ make_counters! { num_send_x86_rel32, num_send_x86_reg, num_send_dynamic, - num_send_inline, - num_send_leaf_builtin, num_send_cfunc, num_send_cfunc_inline, + num_send_iseq, + num_send_iseq_leaf, + num_send_iseq_inline, num_getivar_megamorphic, num_setivar_megamorphic, @@ -795,19 +804,30 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { rb_hash_aset(hash, key, value); } - // Create a hash for the cfunc call counts - let calls_hash = rb_hash_new(); - rb_hash_aset(hash, rust_str_to_sym("cfunc_calls"), calls_hash); - if let Some(cfunc_name_to_idx) = CFUNC_NAME_TO_IDX.as_mut() { - let call_counts = CFUNC_CALL_COUNT.as_mut().unwrap(); - - for (name, idx) in cfunc_name_to_idx { - let count = call_counts[*idx]; - let key = rust_str_to_sym(name); - let value = VALUE::fixnum_from_usize(count as usize); - rb_hash_aset(calls_hash, key, value); + fn set_call_counts( + calls_hash: VALUE, + method_name_to_idx: &mut Option>, + method_call_count: &mut Option>, + ) { + if let (Some(name_to_idx), Some(call_counts)) = (method_name_to_idx, method_call_count) { + for (name, idx) in name_to_idx { + let count = call_counts[*idx]; + let key = rust_str_to_sym(name); + let value = VALUE::fixnum_from_usize(count as usize); + unsafe { rb_hash_aset(calls_hash, key, value); } + } } } + + // Create a hash for the cfunc call counts + let cfunc_calls = rb_hash_new(); + rb_hash_aset(hash, rust_str_to_sym("cfunc_calls"), cfunc_calls); + set_call_counts(cfunc_calls, &mut CFUNC_NAME_TO_IDX, &mut CFUNC_CALL_COUNT); + + // Create a hash for the ISEQ call counts + let iseq_calls = rb_hash_new(); + rb_hash_aset(hash, rust_str_to_sym("iseq_calls"), iseq_calls); + set_call_counts(iseq_calls, &mut ISEQ_NAME_TO_IDX, &mut ISEQ_CALL_COUNT); } hash From bf72cb84ca52bc062cc1711c03622ed6c928fed8 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Fri, 5 Jan 2024 19:45:19 +0900 Subject: [PATCH 110/142] Include the first constant name into `Ractor::IsolationError` message If lhs of assignment is top-level constant reference, the first constant name is omitted from error message. This commit fixes it. ``` # shareable_constant_value: literal ::C = ["Not " + "shareable"] # Before # => cannot assign unshareable object to (Ractor::IsolationError) # After # => cannot assign unshareable object to ::C (Ractor::IsolationError) ``` --- ruby_parser.c | 1 + test/ruby/test_parse.rb | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/ruby_parser.c b/ruby_parser.c index 04a49da0ecdfb8..c48f563744bb4c 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -1060,6 +1060,7 @@ rb_node_const_decl_val(const NODE *node) } else if (n && nd_type_p(n, NODE_COLON3)) { // ::Const::Name + rb_ary_push(path, rb_id2str(RNODE_COLON3(n)->nd_mid)); rb_ary_push(path, rb_str_new(0, 0)); } else { diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 6b8c196a84b04d..b6d306352ce276 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1537,12 +1537,37 @@ class X end def test_shareable_constant_value_unshareable_literal - assert_raise_separately(Ractor::IsolationError, /unshareable/, + assert_raise_separately(Ractor::IsolationError, /unshareable object to C/, "#{<<~"begin;"}\n#{<<~'end;'}") begin; # shareable_constant_value: literal C = ["Not " + "shareable"] end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + B::C = ["Not " + "shareable"] + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do + # shareable_constant_value: literal + ::C = ["Not " + "shareable"] + end + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do + # shareable_constant_value: literal + ::B = Class.new + ::B::C = ["Not " + "shareable"] + end + end; end def test_shareable_constant_value_nonliteral From ea91ab696e6ee7225a2dde909e60dd9d0b241935 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Jan 2024 17:33:20 +0900 Subject: [PATCH 111/142] Fix constant name of `Ractor::IsolationError` message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dest` of `const_decl_path` is `NODE_COLON2` or `NODE_COLON3` in some cases. For example, `B::C ||= [“Not ” + “shareable”]` passes `NODE_COLON2` and `::C ||= [“Not ” + “shareable”]` passes `NODE_COLON3`. This commit fixes `Ractor::IsolationError` message for such case. ``` # shareable_constant_value: literal ::C ||= ["Not " + "shareable"] # Before # => cannot assign unshareable object to C (Ractor::IsolationError) # After # => cannot assign unshareable object to ::C (Ractor::IsolationError) ``` --- ruby_parser.c | 43 +++++++++++++++++++++++++++++------------ test/ruby/test_parse.rb | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/ruby_parser.c b/ruby_parser.c index c48f563744bb4c..7061f2be5b93c4 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -1045,22 +1045,40 @@ VALUE rb_node_const_decl_val(const NODE *node) { VALUE path; - if (RNODE_CDECL(node)->nd_vid) { - path = rb_id2str(RNODE_CDECL(node)->nd_vid); + switch (nd_type(node)) { + case NODE_CDECL: + if (RNODE_CDECL(node)->nd_vid) { + path = rb_id2str(RNODE_CDECL(node)->nd_vid); + goto end; + } + else { + node = RNODE_CDECL(node)->nd_else; + } + break; + case NODE_COLON2: + break; + case NODE_COLON3: + // ::Const + path = rb_str_new_cstr("::"); + rb_str_append(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); + goto end; + default: + rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); + UNREACHABLE_RETURN(0); } - else { - NODE *n = RNODE_CDECL(node)->nd_else; - path = rb_ary_new(); - for (; n && nd_type_p(n, NODE_COLON2); n = RNODE_COLON2(n)->nd_head) { - rb_ary_push(path, rb_id2str(RNODE_COLON2(n)->nd_mid)); + + path = rb_ary_new(); + if (node) { + for (; node && nd_type_p(node, NODE_COLON2); node = RNODE_COLON2(node)->nd_head) { + rb_ary_push(path, rb_id2str(RNODE_COLON2(node)->nd_mid)); } - if (n && nd_type_p(n, NODE_CONST)) { + if (node && nd_type_p(node, NODE_CONST)) { // Const::Name - rb_ary_push(path, rb_id2str(RNODE_CONST(n)->nd_vid)); + rb_ary_push(path, rb_id2str(RNODE_CONST(node)->nd_vid)); } - else if (n && nd_type_p(n, NODE_COLON3)) { + else if (node && nd_type_p(node, NODE_COLON3)) { // ::Const::Name - rb_ary_push(path, rb_id2str(RNODE_COLON3(n)->nd_mid)); + rb_ary_push(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); rb_ary_push(path, rb_str_new(0, 0)); } else { @@ -1068,7 +1086,8 @@ rb_node_const_decl_val(const NODE *node) rb_ary_push(path, rb_str_new_cstr("...")); } path = rb_ary_join(rb_ary_reverse(path), rb_str_new_cstr("::")); - path = rb_fstring(path); } + end: + path = rb_fstring(path); return path; } diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index b6d306352ce276..edf61db3250619 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1568,6 +1568,40 @@ def test_shareable_constant_value_unshareable_literal ::B::C = ["Not " + "shareable"] end end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do + # shareable_constant_value: literal + ::C ||= ["Not " + "shareable"] + end + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + B::C ||= ["Not " + "shareable"] + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do + # shareable_constant_value: literal + ::B = Class.new + ::B::C ||= ["Not " + "shareable"] + end + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to ...::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + def self.expr; B; end + expr::C ||= ["Not " + "shareable"] + end; end def test_shareable_constant_value_nonliteral From 6cafbd35d970f898eebc8c2d7b6f79fb4f79308c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 10 Feb 2024 14:24:28 +0900 Subject: [PATCH 112/142] YJIT: Remove unused variables --- yjit.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/yjit.rb b/yjit.rb index 0a649e0dc71ddc..18b00c07650c84 100644 --- a/yjit.rb +++ b/yjit.rb @@ -401,7 +401,6 @@ def print_sorted_method_calls(calls, num_calls, out:, type:, how_many: 20, left_ top_n_total = pairs.sum { |name, count| count } top_n_pct = 100.0 * top_n_total / num_calls - longest_name_len = pairs.max_by { |name, count| name.length }.first.length out.puts "Top-#{pairs.size} most frequent #{type} calls (#{"%.1f" % top_n_pct}% of #{type} calls):" @@ -429,7 +428,6 @@ def print_sorted_exit_counts(stats, out:, prefix:, how_many: 20, left_pad: 4) # out.puts "Top-#{exits.size} most frequent exit ops (#{"%.1f" % top_n_exit_pct}% of exits):" - longest_insn_name_len = exits.max_by { |name, count| name.length }.first.length exits.each do |name, count| padded_count = format_number_pct(10, count, total_exits) out.puts("#{padded_count}: #{name}") From fdd92c2d61cc8eb86b187d8f116ed640c959a62c Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 10 Feb 2024 14:04:38 +0900 Subject: [PATCH 113/142] Fix the variable to be checked It should check the result of `rb_parser_search_nonascii`. --- parse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse.y b/parse.y index e14fb8eb699040..366afa3d206534 100644 --- a/parse.y +++ b/parse.y @@ -2265,7 +2265,7 @@ rb_parser_coderange_scan(struct parser_params *p, const char *ptr, long len, rb_ if (enc == rb_ascii8bit_encoding()) { /* enc is ASCII-8BIT. ASCII-8BIT string never be broken. */ ptr = rb_parser_search_nonascii(ptr, e); - return p ? RB_PARSER_ENC_CODERANGE_VALID : RB_PARSER_ENC_CODERANGE_7BIT; + return ptr ? RB_PARSER_ENC_CODERANGE_VALID : RB_PARSER_ENC_CODERANGE_7BIT; } /* parser string encoding is always asciicompat */ From f960fbc10256ee227ad6887089388e0bda59aab5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 10 Feb 2024 22:56:32 +0900 Subject: [PATCH 114/142] [ruby/optparse] Search exactly when `require_exact` To work with options defined as `--[no]-something`. Fix https://bugs.ruby-lang.org/issues/20252 Fix https://github.com/ruby/optparse/pull/60 https://github.com/ruby/optparse/commit/78afdab307 --- lib/optparse.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index fbcd7f9746f756..8382364977ae8a 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1054,7 +1054,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: # Shows option summary. # Officious['help'] = proc do |parser| - Switch::NoArgument.new(nil, nil, ["-h"], ["--help"]) do |arg| + Switch::NoArgument.new do |arg| puts parser.help exit end @@ -1479,7 +1479,7 @@ def make_switch(opts, block = nil) default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end - ldesc << "--#{q}" << "--no-#{q}" + ldesc << "--[no-]#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style @@ -1654,14 +1654,19 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt, rest = $1, $2 opt.tr!('_', '-') begin - sw, = complete(:long, opt, true) - if require_exact && !sw.long.include?("--#{opt}") - throw :terminate, arg unless raise_unknown - raise InvalidOption, arg + if require_exact + sw, = search(:long, opt) + else + sw, = complete(:long, opt, true) end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) + else + unless sw + throw :terminate, arg unless raise_unknown + raise InvalidOption, arg + end end begin opt, cb, *val = sw.parse(rest, argv) {|*exc| raise(*exc)} From 429eeb09f25bd1bd3f64f70c6ef409bedd3c6c1f Mon Sep 17 00:00:00 2001 From: Ignacio Chiazzo Cardarello Date: Sat, 10 Feb 2024 19:07:48 -0300 Subject: [PATCH 115/142] [ruby/irb] Introduce exit! command (https://github.com/ruby/irb/pull/851) * Added failing test for when writing history on exit * Save history on exit * Exit early when calling Kernel.exit * use status 0 for kernel.exit * Added test for nested sessions * Update lib/irb.rb --------- https://github.com/ruby/irb/commit/c0a5f31679 Co-authored-by: Stan Lo --- lib/irb.rb | 21 +++++++++++++--- lib/irb/cmd/exit_forced_action.rb | 22 +++++++++++++++++ lib/irb/extend-command.rb | 5 ++++ test/irb/test_debug_cmd.rb | 41 +++++++++++++++++++++++++++++++ test/irb/test_history.rb | 18 ++++++++++++++ 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 lib/irb/cmd/exit_forced_action.rb diff --git a/lib/irb.rb b/lib/irb.rb index f3abed820039c4..ad6ec78aa41327 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -886,7 +886,11 @@ def IRB.start(ap_path = nil) # Quits irb def IRB.irb_exit(*) - throw :IRB_EXIT + throw :IRB_EXIT, false + end + + def IRB.irb_exit!(*) + throw :IRB_EXIT, true end # Aborts then interrupts irb. @@ -968,7 +972,8 @@ def run(conf = IRB.conf) conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context - save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? + supports_history_saving = conf[:SAVE_HISTORY] && context.io.support_history_saving? + save_history = !in_nested_session && supports_history_saving if save_history context.io.load_history @@ -979,13 +984,21 @@ def run(conf = IRB.conf) end begin - catch(:IRB_EXIT) do + forced_exit = false + + forced_exit = catch(:IRB_EXIT) do eval_input end ensure trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} - context.io.save_history if save_history + + if forced_exit + context.io.save_history if supports_history_saving + Kernel.exit(0) + else + context.io.save_history if save_history + end end end diff --git a/lib/irb/cmd/exit_forced_action.rb b/lib/irb/cmd/exit_forced_action.rb new file mode 100644 index 00000000000000..e5df75b682106d --- /dev/null +++ b/lib/irb/cmd/exit_forced_action.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class ExitForcedAction < Nop + category "IRB" + description "Exit the current process." + + def execute(*) + IRB.irb_exit! + rescue UncaughtThrowError + Kernel.exit(0) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 91ca96e91a741a..2db2b80578c7f8 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -36,6 +36,11 @@ def irb_context [:quit, OVERRIDE_PRIVATE_ONLY], [:irb_quit, OVERRIDE_PRIVATE_ONLY], ], + [ + :irb_exit!, :ExitForcedAction, "cmd/exit_forced_action", + [:exit!, OVERRIDE_PRIVATE_ONLY], + ], + [ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", [:cwws, NO_OVERRIDE], diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index 0fb45af478e8cb..cbd01200933196 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -255,6 +255,47 @@ def test_exit assert_match(/irb\(main\):001> next/, output) end + def test_forced_exit_finishes_process_when_nested_sessions + write_ruby <<~'ruby' + puts "First line" + puts "Second line" + binding.irb + puts "Third line" + binding.irb + puts "Fourth line" + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/First line\r\n/, output) + assert_match(/Second line\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + refute_match(/Third line\r\n/, output) + refute_match(/Fourth line\r\n/, output) + end + + def test_forced_exit + write_ruby <<~'ruby' + puts "Hello" + binding.irb + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/Hello\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + end + def test_quit write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index e6448ada37a38f..49f3698f924fb1 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -379,6 +379,24 @@ def foo HISTORY end + def test_history_saving_with_exit! + write_history "" + + write_ruby <<~'RUBY' + binding.irb + RUBY + + run_ruby_file do + type "'starting session'" + type "exit!" + end + + assert_equal <<~HISTORY, @history_file.open.read + 'starting session' + exit! + HISTORY + end + def test_history_saving_with_nested_sessions_and_prior_history write_history <<~HISTORY old_history_1 From 5c4657f8832bcbb9e7c3c50b6fe69212a86de153 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sun, 11 Feb 2024 05:17:37 +0000 Subject: [PATCH 116/142] [ruby/irb] Polish the exit! command and its tests (https://github.com/ruby/irb/pull/867) * Remove IRB.irb_exit! method It's not necessary to introduce a new method just for the exit! command at this moment. * Rename ExitForcedAction to ForceExit * Move force exit tests to a dedicated file * Fix nested history saving with exit! command Because we switched to use `Kernel#exit` instead of `exit!`, the outer session's ensure block in `Irb#run` will be run, which will save the history. This means the separate check to save history when force exiting is no longer necessary. * execute_lines helper should also capture IRB setup's output This prevents setup warnings from being printed to test output while allowing those output to be tested. * Update readme https://github.com/ruby/irb/commit/899d10ade1 --- lib/irb.rb | 15 ++---- .../{exit_forced_action.rb => force_exit.rb} | 4 +- lib/irb/extend-command.rb | 2 +- test/irb/cmd/test_force_exit.rb | 51 +++++++++++++++++++ test/irb/test_cmd.rb | 24 +++++---- test/irb/test_debug_cmd.rb | 41 --------------- test/irb/test_history.rb | 44 ++++++++++++++-- 7 files changed, 111 insertions(+), 70 deletions(-) rename lib/irb/cmd/{exit_forced_action.rb => force_exit.rb} (83%) create mode 100644 test/irb/cmd/test_force_exit.rb diff --git a/lib/irb.rb b/lib/irb.rb index ad6ec78aa41327..3830867e6aee8c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -889,10 +889,6 @@ def IRB.irb_exit(*) throw :IRB_EXIT, false end - def IRB.irb_exit!(*) - throw :IRB_EXIT, true - end - # Aborts then interrupts irb. # # Will raise an Abort exception, or the given +exception+. @@ -972,8 +968,7 @@ def run(conf = IRB.conf) conf[:IRB_RC].call(context) if conf[:IRB_RC] conf[:MAIN_CONTEXT] = context - supports_history_saving = conf[:SAVE_HISTORY] && context.io.support_history_saving? - save_history = !in_nested_session && supports_history_saving + save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? if save_history context.io.load_history @@ -993,12 +988,8 @@ def run(conf = IRB.conf) trap("SIGINT", prev_trap) conf[:AT_EXIT].each{|hook| hook.call} - if forced_exit - context.io.save_history if supports_history_saving - Kernel.exit(0) - else - context.io.save_history if save_history - end + context.io.save_history if save_history + Kernel.exit(0) if forced_exit end end diff --git a/lib/irb/cmd/exit_forced_action.rb b/lib/irb/cmd/force_exit.rb similarity index 83% rename from lib/irb/cmd/exit_forced_action.rb rename to lib/irb/cmd/force_exit.rb index e5df75b682106d..2b9f296865c0ad 100644 --- a/lib/irb/cmd/exit_forced_action.rb +++ b/lib/irb/cmd/force_exit.rb @@ -6,12 +6,12 @@ module IRB # :stopdoc: module ExtendCommand - class ExitForcedAction < Nop + class ForceExit < Nop category "IRB" description "Exit the current process." def execute(*) - IRB.irb_exit! + throw :IRB_EXIT, true rescue UncaughtThrowError Kernel.exit(0) end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 2db2b80578c7f8..d303bf76dae74f 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -37,7 +37,7 @@ def irb_context [:irb_quit, OVERRIDE_PRIVATE_ONLY], ], [ - :irb_exit!, :ExitForcedAction, "cmd/exit_forced_action", + :irb_exit!, :ForceExit, "cmd/force_exit", [:exit!, OVERRIDE_PRIVATE_ONLY], ], diff --git a/test/irb/cmd/test_force_exit.rb b/test/irb/cmd/test_force_exit.rb new file mode 100644 index 00000000000000..191a7868721e3e --- /dev/null +++ b/test/irb/cmd/test_force_exit.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: false +require 'irb' + +require_relative "../helper" + +module TestIRB + class ForceExitTest < IntegrationTestCase + def test_forced_exit_finishes_process_immediately + write_ruby <<~'ruby' + puts "First line" + puts "Second line" + binding.irb + puts "Third line" + binding.irb + puts "Fourth line" + ruby + + output = run_ruby_file do + type "123" + type "456" + type "exit!" + end + + assert_match(/First line\r\n/, output) + assert_match(/Second line\r\n/, output) + assert_match(/irb\(main\):001> 123/, output) + assert_match(/irb\(main\):002> 456/, output) + refute_match(/Third line\r\n/, output) + refute_match(/Fourth line\r\n/, output) + end + + def test_forced_exit_in_nested_sessions + write_ruby <<~'ruby' + def foo + binding.irb + end + + binding.irb + binding.irb + ruby + + output = run_ruby_file do + type "123" + type "foo" + type "exit!" + end + + assert_match(/irb\(main\):001> 123/, output) + end + end +end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index d99ac05c5dbb63..7d7353281e2c69 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -34,17 +34,17 @@ def teardown end def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:PROMPT_MODE] = :SIMPLE - IRB.conf[:USE_PAGER] = false - IRB.conf.merge!(conf) - input = TestInputMethod.new(lines) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - irb.context.irb_path = irb_path if irb_path - IRB.conf[:MAIN_CONTEXT] = irb.context capture_output do + IRB.init_config(nil) + IRB.conf[:VERBOSE] = false + IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:USE_PAGER] = false + IRB.conf.merge!(conf) + input = TestInputMethod.new(lines) + irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) + irb.context.return_format = "=> %s\n" + irb.context.irb_path = irb_path if irb_path + IRB.conf[:MAIN_CONTEXT] = irb.context irb.eval_input end end @@ -58,7 +58,9 @@ def test_calling_command_on_a_frozen_main "irb_info", main: main ) - assert_empty err + # Because the main object is frozen, IRB would wrap a delegator around it + # Which's exit! method can't be overridden and would raise a warning + assert_match(/delegator does not forward private method #exit\!/, err) assert_match(/RUBY_PLATFORM/, out) end end diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index cbd01200933196..0fb45af478e8cb 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -255,47 +255,6 @@ def test_exit assert_match(/irb\(main\):001> next/, output) end - def test_forced_exit_finishes_process_when_nested_sessions - write_ruby <<~'ruby' - puts "First line" - puts "Second line" - binding.irb - puts "Third line" - binding.irb - puts "Fourth line" - ruby - - output = run_ruby_file do - type "123" - type "456" - type "exit!" - end - - assert_match(/First line\r\n/, output) - assert_match(/Second line\r\n/, output) - assert_match(/irb\(main\):001> 123/, output) - assert_match(/irb\(main\):002> 456/, output) - refute_match(/Third line\r\n/, output) - refute_match(/Fourth line\r\n/, output) - end - - def test_forced_exit - write_ruby <<~'ruby' - puts "Hello" - binding.irb - ruby - - output = run_ruby_file do - type "123" - type "456" - type "exit!" - end - - assert_match(/Hello\r\n/, output) - assert_match(/irb\(main\):001> 123/, output) - assert_match(/irb\(main\):002> 456/, output) - end - def test_quit write_ruby <<~'RUBY' binding.irb diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 49f3698f924fb1..2601bcad844f66 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -379,20 +379,58 @@ def foo HISTORY end - def test_history_saving_with_exit! + def test_nested_history_saving_from_inner_session_with_exit! write_history "" write_ruby <<~'RUBY' + def foo + binding.irb + end + binding.irb RUBY run_ruby_file do - type "'starting session'" + type "'outer session'" + type "foo" + type "'inner session'" type "exit!" end assert_equal <<~HISTORY, @history_file.open.read - 'starting session' + 'outer session' + foo + 'inner session' + exit! + HISTORY + end + + def test_nested_history_saving_from_outer_session_with_exit! + write_history "" + + write_ruby <<~'RUBY' + def foo + binding.irb + end + + binding.irb + RUBY + + run_ruby_file do + type "'outer session'" + type "foo" + type "'inner session'" + type "exit" + type "'outer session again'" + type "exit!" + end + + assert_equal <<~HISTORY, @history_file.open.read + 'outer session' + foo + 'inner session' + exit + 'outer session again' exit! HISTORY end From aa36e44c05380386b583c62f6596b92098cb9ad1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Feb 2024 19:34:13 +0900 Subject: [PATCH 117/142] Win32: Define `HAVE_INTTYPES_H` Suppress redefinition warnings, inttypes.h has been provided as well as stdint.h since `_MSC_VER` 1600 (= Visual C++ 10.0 = Visual Studio 2010). ``` C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(123): warning C4005: 'PRIdPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(51): note: see previous definition of 'PRIdPTR' C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(142): warning C4005: 'PRIiPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(52): note: see previous definition of 'PRIiPTR' C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(161): warning C4005: 'PRIoPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(53): note: see previous definition of 'PRIoPTR' C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(180): warning C4005: 'PRIuPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(54): note: see previous definition of 'PRIuPTR' C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(199): warning C4005: 'PRIxPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(55): note: see previous definition of 'PRIxPTR' C:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt\inttypes.h(218): warning C4005: 'PRIXPTR': macro redefinition D:\a\ruby\ruby\src\include\ruby/backward/2/inttypes.h(56): note: see previous definition of 'PRIXPTR' ``` --- win32/Makefile.sub | 1 + 1 file changed, 1 insertion(+) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 3d4bbc87e8b1b3..009c64053616e0 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -760,6 +760,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_STRUCT_TIMESPEC !endif !if $(MSC_VER) >= 1600 +#define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 !else #define int8_t signed char From c77f736bc1e6b73d62e9ba3dcf3dcbeff0657b38 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Feb 2024 19:43:06 +0900 Subject: [PATCH 118/142] Win32: Fix pre-defined macros for platforms Use `_WIN64` for word-size, `_M_AMD64` for CPU-specific feature. --- include/ruby/atomic.h | 16 ++++++++++------ include/ruby/internal/memory.h | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index eb106631f6e543..043a6a9945cf2a 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -424,7 +424,7 @@ rbimpl_atomic_size_add(volatile size_t *ptr, size_t val) #elif defined(HAVE_GCC_SYNC_BUILTINS) __sync_add_and_fetch(ptr, val); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) /* Ditto for `InterlockeExchangedAdd`. */ InterlockedExchangeAdd64(ptr, val); @@ -476,13 +476,15 @@ rbimpl_atomic_size_inc(volatile size_t *ptr) #elif defined(HAVE_GCC_ATOMIC_BUILTINS) || defined(HAVE_GCC_SYNC_BUILTINS) rbimpl_atomic_size_add(ptr, 1); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) InterlockedIncrement64(ptr); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_inc_ulong(ptr); #else + RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); + rbimpl_atomic_size_add(ptr, 1); #endif @@ -558,7 +560,7 @@ rbimpl_atomic_size_sub(volatile size_t *ptr, size_t val) #elif defined(HAVE_GCC_SYNC_BUILTINS) __sync_sub_and_fetch(ptr, val); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) const ssize_t neg = -1; InterlockedExchangeAdd64(ptr, neg * val); @@ -610,13 +612,15 @@ rbimpl_atomic_size_dec(volatile size_t *ptr) #elif defined(HAVE_GCC_ATOMIC_BUILTINS) || defined(HAVE_GCC_SYNC_BUILTINS) rbimpl_atomic_size_sub(ptr, 1); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) InterlockedDecrement64(ptr); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_dec_ulong(ptr); #else + RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); + rbimpl_atomic_size_sub(ptr, 1); #endif @@ -708,7 +712,7 @@ rbimpl_atomic_size_exchange(volatile size_t *ptr, size_t val) #elif defined(HAVE_GCC_SYNC_BUILTINS) return __sync_lock_test_and_set(ptr, val); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) return InterlockedExchange64(ptr, val); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) @@ -858,7 +862,7 @@ rbimpl_atomic_size_cas(volatile size_t *ptr, size_t oldval, size_t newval) #elif defined(HAVE_GCC_SYNC_BUILTINS) return __sync_val_compare_and_swap(ptr, oldval, newval); -#elif defined(_WIN32) && defined(_M_AMD64) +#elif defined(_WIN64) return InterlockedCompareExchange64(ptr, newval, oldval); #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index 8f007875120558..3bd54cd6c74ebf 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -38,7 +38,7 @@ # include #endif -#if defined(_MSC_VER) && defined(_WIN64) +#if defined(_MSC_VER) && defined(_M_AMD64) # include # pragma intrinsic(_umul128) #endif From ea2ea74a261da3cc954291ffdef9805ee59a80f7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Feb 2024 20:03:18 +0900 Subject: [PATCH 119/142] Win32: Copy coroutine no longer exists At 42130a64f02294dc8025af3a51bda518c67ab33d, it has been replaced with pthread implementation. --- win32/Makefile.sub | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 009c64053616e0..7792d5d8e3ba35 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -330,8 +330,7 @@ COROUTINE_SRC = $(COROUTINE_OBJ:.obj=.asm) COROUTINE_OBJ = coroutine/win32/Context.obj COROUTINE_SRC = $(COROUTINE_OBJ:.obj=.asm) !else -COROUTINE_OBJ = coroutine/copy/Context.obj -COROUTINE_SRC = $(COROUTINE_OBJ:.obj=.c) +!error copy coroutine has been replaced with pthread implementation at 42130a64f02294dc8025af3a51bda518c67ab33d !endif COROUTINE_H = $(COROUTINE_OBJ:.obj=.h) From 603392b8d47afd5e517c89b7f883b754b4a2e94b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Feb 2024 20:55:26 +0900 Subject: [PATCH 120/142] Win32: Use prototype --- coroutine/win64/Context.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coroutine/win64/Context.h b/coroutine/win64/Context.h index aaa4caeaf9fc48..d85ebf8e0e48c3 100644 --- a/coroutine/win64/Context.h +++ b/coroutine/win64/Context.h @@ -30,7 +30,7 @@ struct coroutine_context typedef void(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self); -void coroutine_trampoline(); +void coroutine_trampoline(void); static inline void coroutine_initialize_main(struct coroutine_context * context) { context->stack_pointer = NULL; From 90fe1b4402498f61208e9b72ce3206d4bcbffb7d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Feb 2024 22:00:12 +0900 Subject: [PATCH 121/142] Win32: Use `TARGET_OS` for word-size It is derived from `_WIN64` pre-defined macro, at `-osname-` in win32/setup.mak. --- win32/Makefile.sub | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 7792d5d8e3ba35..dba112259baff9 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -680,7 +680,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define _INTEGRAL_MAX_BITS 64 #endif #define SIZEOF_OFF_T 8 -!if "$(ARCH)" == "x64" +!if "$(TARGET_OS)" == "mswin64" #define SIZEOF_VOIDP 8 !else #define SIZEOF_VOIDP 4 @@ -702,7 +702,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define NUM2CLOCKID(v) NUM2INT(v) #define SIZEOF_CLOCK_T 4 #define SIZEOF_RLIM_T 0 -!if "$(ARCH)" == "x64" +!if "$(TARGET_OS)" == "mswin64" #define SIZEOF_SIZE_T 8 #define SIZEOF_PTRDIFF_T 8 #define SIZEOF_INTPTR_T 8 @@ -802,7 +802,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_INTPTR_T 1 #define HAVE_UINTPTR_T 1 #define HAVE_SSIZE_T 1 -!if "$(ARCH)" == "x64" +!if "$(TARGET_OS)" == "mswin64" #define ssize_t __int64 #define PRI_PTR_PREFIX "I64" !else From a3ceb6916828b3d2e14b4d4bb4f8c78a0bc2ed95 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 6 Feb 2024 17:13:50 -0500 Subject: [PATCH 122/142] [PRISM] Fix error handling in `pm_parse_prism` Following changes made in ruby/prism#2365 this implements error handling for when `pm_string_mapped_init` returns `false`. Related: ruby/prism#2207 --- prism_compile.c | 8 +++++++- test/ruby/test_compile_prism.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index 764d2fa557f2b5..0e507b56d90fbd 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -8153,7 +8153,13 @@ VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath) { if (!pm_string_mapped_init(&result->input, RSTRING_PTR(filepath))) { - return rb_exc_new3(rb_eRuntimeError, rb_sprintf("Failed to map file: %s", RSTRING_PTR(filepath))); +#ifdef _WIN32 + int e = rb_w32_map_errno(GetLastError()); +#else + int e = errno; +#endif + + return rb_syserr_new(e, RSTRING_PTR(filepath)); } VALUE error = pm_parse_input(result, filepath); diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index e09192a4b24311..c48b7ac085ad53 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -2678,6 +2678,22 @@ def test_encoding assert_prism_eval(":però") end + def test_parse_file + assert_nothing_raised do + RubyVM::InstructionSequence.compile_file_prism(__FILE__) + end + + error = assert_raise Errno::ENOENT do + RubyVM::InstructionSequence.compile_file_prism("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + RubyVM::InstructionSequence.compile_file_prism(nil) + end + end + private def compare_eval(source, raw:) From bbccabe6d67c83ed81028e4c5ca9daaa4f6f7531 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 01:07:05 +0900 Subject: [PATCH 123/142] [ruby/optparse] [DOC] Add missing documents https://github.com/ruby/optparse/commit/33956ce93f --- lib/optparse.rb | 34 ++++++++++++++++++++++++++++++---- lib/optparse/ac.rb | 16 ++++++++++++++++ lib/optparse/version.rb | 9 +++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 8382364977ae8a..438a09fbf68eb5 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -8,7 +8,6 @@ # See OptionParser for documentation. # - #-- # == Developer Documentation (not for RDoc output) # @@ -425,6 +424,7 @@ # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class OptionParser + # The version string OptionParser::Version = "0.4.0" # :stopdoc: @@ -438,6 +438,8 @@ class OptionParser # and resolved against a list of acceptable values. # module Completion + # :nodoc: + def self.regexp(key, icase) Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) end @@ -510,6 +512,8 @@ class OptionMap < Hash # RequiredArgument, etc. # class Switch + # :nodoc: + attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # @@ -715,10 +719,10 @@ def parse(arg, argv) conv_arg(arg) end - def self.incompatible_argument_styles(*) + def self.incompatible_argument_styles(*) # :nodoc: end - def self.pattern + def self.pattern # :nodoc: Object end @@ -804,6 +808,8 @@ def pretty_head # :nodoc: # matching pattern and converter pair. Also provides summary feature. # class List + # :nodoc: + # Map from acceptable argument types to pattern and converter pairs. attr_reader :atype @@ -1185,6 +1191,11 @@ def self.terminate(arg = nil) end @stack = [DefaultList] + # + # Returns the global top option list. + # + # Do not use directly. + # def self.top() DefaultList end # @@ -1297,10 +1308,24 @@ def ver end end + # + # Shows warning message with the program name + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#warn. + # def warn(mesg = $!) super("#{program_name}: #{mesg}") end + # + # Shows message with the program name then aborts. + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#abort. + # def abort(mesg = $!) super("#{program_name}: #{mesg}") end @@ -2342,7 +2367,8 @@ def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end - def initialize(*args) + + def initialize(*args) # :nodoc: super @optparse = nil end diff --git a/lib/optparse/ac.rb b/lib/optparse/ac.rb index 0953725e46ed8e..23fc740d100300 100644 --- a/lib/optparse/ac.rb +++ b/lib/optparse/ac.rb @@ -1,7 +1,11 @@ # frozen_string_literal: false require_relative '../optparse' +# +# autoconf-like options. +# class OptionParser::AC < OptionParser + # :stopdoc: private def _check_ac_args(name, block) @@ -14,6 +18,7 @@ def _check_ac_args(name, block) end ARG_CONV = proc {|val| val.nil? ? true : val} + private_constant :ARG_CONV def _ac_arg_enable(prefix, name, help_string, block) _check_ac_args(name, block) @@ -29,16 +34,27 @@ def _ac_arg_enable(prefix, name, help_string, block) enable end + # :startdoc: + public + # Define --enable / --disable style option + # + # Appears as --enable-name in help message. def ac_arg_enable(name, help_string, &block) _ac_arg_enable("enable", name, help_string, block) end + # Define --enable / --disable style option + # + # Appears as --disable-name in help message. def ac_arg_disable(name, help_string, &block) _ac_arg_enable("disable", name, help_string, block) end + # Define --with / --without style option + # + # Appears as --with-name in help message. def ac_arg_with(name, help_string, &block) _check_ac_args(name, block) diff --git a/lib/optparse/version.rb b/lib/optparse/version.rb index b869d8fe51ed47..b5ed6951460bb1 100644 --- a/lib/optparse/version.rb +++ b/lib/optparse/version.rb @@ -2,6 +2,11 @@ # OptionParser internal utility class << OptionParser + # + # Shows version string in packages if Version is defined. + # + # +pkgs+:: package list + # def show_version(*pkgs) progname = ARGV.options.program_name result = false @@ -47,6 +52,8 @@ def show_version(*pkgs) result end + # :stopdoc: + def each_const(path, base = ::Object) path.split(/::|\//).inject(base) do |klass, name| raise NameError, path unless Module === klass @@ -68,4 +75,6 @@ def search_const(klass, name) end end end + + # :startdoc: end From a63a0c247b9630de2bccdc68f04711d528c5dc82 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 01:09:51 +0900 Subject: [PATCH 124/142] Win32: Include stdio.h for `printf` --- win32/setup.mak | 1 + 1 file changed, 1 insertion(+) diff --git a/win32/setup.mak b/win32/setup.mak index c0074bf963db4c..d4178127bf2282 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -129,6 +129,7 @@ vs2022-fp-bug: /* compile with -O2 */ #include #include +#include #define value_finite(d) 'f' #define value_infinity() 'i' From c9006ddb88111cf010d5aa2a9e28032623b9ac93 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 01:30:58 +0900 Subject: [PATCH 125/142] [ruby/optparse] [DOC] Add description of OptionParser#define_by_keywords https://github.com/ruby/optparse/commit/451dea51a0 --- lib/optparse/kwargs.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/optparse/kwargs.rb b/lib/optparse/kwargs.rb index 992aca467eb870..59a2f61544f9e5 100644 --- a/lib/optparse/kwargs.rb +++ b/lib/optparse/kwargs.rb @@ -7,12 +7,17 @@ class OptionParser # # :include: ../../doc/optparse/creates_option.rdoc # - def define_by_keywords(options, meth, **opts) - meth.parameters.each do |type, name| + # Defines options which set in to _options_ for keyword parameters + # of _method_. + # + # Parameters for each keywords are given as elements of _params_. + # + def define_by_keywords(options, method, **params) + method.parameters.each do |type, name| case type when :key, :keyreq op, cl = *(type == :key ? %w"[ ]" : ["", ""]) - define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o| + define("--#{name}=#{op}#{name.upcase}#{cl}", *params[name]) do |o| options[name] = o end end From 1d467f2255112f9e712d5d9aa6f2cd0a102fb56e Mon Sep 17 00:00:00 2001 From: KJ Tsanaktsidis Date: Mon, 12 Feb 2024 09:27:16 +1100 Subject: [PATCH 126/142] Burn default ASAN options into the built Ruby * We always need use_sigaltstack=0 because Ruby registers sigaltstack handlers * We also need to disable leak detection (unless RUBY_FREE_AT_EXIT is set - I might experiment later with automatically enabling leak detection if RUBY_FREE_AT_EXIT is set). Burning it into the built ruby binary in this way avoids people needing to remember to start their Ruby program with these flags all the time. We also need a small fix in mkmf to make sure that test programs also don't have leak detection enabled (this is never desirable) [Bug #20256] --- common.mk | 4 ++++ lib/mkmf.rb | 5 +++++ main.c | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/common.mk b/common.mk index 53bd710a466486..68ffdf40769e52 100644 --- a/common.mk +++ b/common.mk @@ -9325,11 +9325,15 @@ localeinit.$(OBJEXT): {$(VPATH)}st.h localeinit.$(OBJEXT): {$(VPATH)}subst.h main.$(OBJEXT): $(hdrdir)/ruby.h main.$(OBJEXT): $(hdrdir)/ruby/ruby.h +main.$(OBJEXT): $(top_srcdir)/internal/compilers.h +main.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h +main.$(OBJEXT): $(top_srcdir)/internal/warnings.h main.$(OBJEXT): {$(VPATH)}assert.h main.$(OBJEXT): {$(VPATH)}backward.h main.$(OBJEXT): {$(VPATH)}backward/2/assume.h main.$(OBJEXT): {$(VPATH)}backward/2/attributes.h main.$(OBJEXT): {$(VPATH)}backward/2/bool.h +main.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h main.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h main.$(OBJEXT): {$(VPATH)}backward/2/limits.h main.$(OBJEXT): {$(VPATH)}backward/2/long_long.h diff --git a/lib/mkmf.rb b/lib/mkmf.rb index c8f30747337fe3..f59c19c5542589 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -399,6 +399,11 @@ def expand_command(commands, envs = libpath_env) env, *commands = commands if Hash === commands.first envs.merge!(env) if env end + + # disable ASAN leak reporting - conftest programs almost always don't bother + # to free their memory. + envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS') + return envs, expand[commands] end diff --git a/main.c b/main.c index 072dc56dd54c11..d434f84c40f09f 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,7 @@ #undef RUBY_EXPORT #include "ruby.h" #include "vm_debug.h" +#include "internal/sanitizers.h" #ifdef HAVE_LOCALE_H #include #endif @@ -57,3 +58,12 @@ main(int argc, char **argv) ruby_sysinit(&argc, &argv); return rb_main(argc, argv); } + +#ifdef RUBY_ASAN_ENABLED +/* Compile in the ASAN options Ruby needs, rather than relying on environment variables, so + * that even tests which fork ruby with a clean environment will run ASAN with the right + * settings */ +const char *__asan_default_options(void) { + return "use_sigaltstack=0:detect_leaks=0"; +} +#endif From 697ade7bda5942e372c8d6ba450dd534f0cf186f Mon Sep 17 00:00:00 2001 From: KJ Tsanaktsidis Date: Mon, 12 Feb 2024 09:57:00 +1100 Subject: [PATCH 127/142] Update ASAN docs to reflect the current state of things I don't really think ASAN works well at all on any version of Ruby from before https://bugs.ruby-lang.org/issues/20001 was landed. Update the docs to clarify what works, and what does not work. Also there's no need to compile at `-O0`; this was probably just hiding some of the problems with our stack scanning that were fixed in the above issue. [Bug #20248] --- doc/contributing/building_ruby.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 411e9a60f957a8..627ac025eee58e 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -173,17 +173,23 @@ You should configure Ruby without optimization and other flags that may interfer ### Building with Address Sanitizer -Using the address sanitizer is a great way to detect memory issues. +Using the address sanitizer (ASAN) is a great way to detect memory issues. It can detect memory safety issues in Ruby itself, and also in any C extensions compiled with and loaded into a Ruby compiled with ASAN. ``` shell ./autogen.sh mkdir build && cd build -export ASAN_OPTIONS="halt_on_error=0:use_sigaltstack=0:detect_leaks=0" -../configure cppflags="-fsanitize=address -fno-omit-frame-pointer" optflags=-O0 LDFLAGS="-fsanitize=address -fno-omit-frame-pointer" +../configure CC=clang cflags="-fsanitize=address -fno-omit-frame-pointer -DUSE_MN_THREADS=0" # and any other options you might like make ``` +The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. -On Linux it is important to specify `-O0` when debugging. This is especially true for ASAN which sometimes works incorrectly at higher optimisation levels. +Please note, however, the following caveats! + +* ASAN will not work properly on any currently released version of Ruby; the necessary support is currently only present on Ruby's master branch. +* Due to [this bug](https://bugs.ruby-lang.org/issues/20243), Clang generates code for threadlocal variables which doesn't work with M:N threading. Thus, it's necessary to disable M:N threading support at build time for now. +* Currently, ASAN will only work correctly when using a recent head build of LLVM/Clang - it requires [this bugfix](https://github.com/llvm/llvm-project/pull/75290) related to multithreaded `fork`, which is not yet in any released version. See [here](https://llvm.org/docs/CMake.html) for instructions on how to build LLVM/Clang from source (note you will need at least the `clang` and `copmiler-rt` projects enabled). Then, you will need to replace `CC=clang` in the instructions with an explicit path to your built Clang binary. +* ASAN has only been tested so far with Clang on Linux. It may or may not work with other compilers or on other platforms - please file an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if you run into problems with such configurations (or, to report that they actually work properly!) +* In particular, although I have not yet tried it, I have reason to believe ASAN will _not_ work properly on macOS yet - the fix for the multithreaded fork issue was actually reverted for macOS (see [here](https://github.com/llvm/llvm-project/commit/2a03854e4ce9bb1bcd79a211063bc63c4657f92c)). Please open an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if this is a problem for you. ## How to measure coverage of C and Ruby code From 93accfdf48d38160a11c2f0f8ed978f4cdbfe3cd Mon Sep 17 00:00:00 2001 From: yui-knk Date: Mon, 12 Feb 2024 13:50:27 +0900 Subject: [PATCH 128/142] Fix [BUG] unknown node for NODE_ENCODING It should be `return` instead of `break`, otherwise `[BUG] dump_node: unknown node: NODE_ENCODING` happens. --- node_dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_dump.c b/node_dump.c index 5bf95911153d17..69ab98c8837adb 100644 --- a/node_dump.c +++ b/node_dump.c @@ -1155,7 +1155,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: [enc]"); ANN("example: __ENCODING__"); F_VALUE(enc, rb_node_encoding_val(node), "enc"); - break; + return; case NODE_ERROR: ANN("Broken input recovered by Error Tolerant mode"); From c20e819e8b04e84a4103ca9a036022543459c213 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 30 Jan 2024 17:15:44 -0800 Subject: [PATCH 129/142] Fix crash when passing large keyword splat to method accepting keywords and keyword splat The following code previously caused a crash: ```ruby h = {} 1000000.times{|i| h[i.to_s.to_sym] = i} def f(kw: 1, **kws) end f(**h) ``` Inside a thread or fiber, the size of the keyword splat could be much smaller and still cause a crash. I found this issue while optimizing method calling by reducing implicit allocations. Given the following code: ```ruby def f(kw: , **kws) end kw = {kw: 1} f(**kw) ``` The `f(**kw)` call previously allocated two hashes callee side instead of a single hash. This is because `setup_parameters_complex` would extract the keywords from the keyword splat hash to the C stack, to attempt to mirror the case when literal keywords are passed without a keyword splat. Then, `make_rest_kw_hash` would build a new hash based on the extracted keywords that weren't used for literal keywords. Switch the implementation so that if a keyword splat is passed, literal keywords are deleted from the keyword splat hash (or a copy of the hash if the hash is not mutable). In addition to avoiding the crash, this new approach is much more efficient in all cases. With the included benchmark: ``` 1 miniruby: 5247879.9 i/s miniruby-before: 2474050.2 i/s - 2.12x slower 1_mutable miniruby: 1797036.5 i/s miniruby-before: 1239543.3 i/s - 1.45x slower 10 miniruby: 1094750.1 i/s miniruby-before: 365529.6 i/s - 2.99x slower 10_mutable miniruby: 407781.7 i/s miniruby-before: 225364.0 i/s - 1.81x slower 100 miniruby: 100992.3 i/s miniruby-before: 32703.6 i/s - 3.09x slower 100_mutable miniruby: 40092.3 i/s miniruby-before: 21266.9 i/s - 1.89x slower 1000 miniruby: 21694.2 i/s miniruby-before: 4949.8 i/s - 4.38x slower 1000_mutable miniruby: 5819.5 i/s miniruby-before: 2995.0 i/s - 1.94x slower ``` --- benchmark/vm_call_kw_and_kw_splat.yml | 25 +++++++ test/ruby/test_keyword.rb | 14 ++++ vm_args.c | 104 +++++++++++++++++++------- 3 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 benchmark/vm_call_kw_and_kw_splat.yml diff --git a/benchmark/vm_call_kw_and_kw_splat.yml b/benchmark/vm_call_kw_and_kw_splat.yml new file mode 100644 index 00000000000000..aa6e549e0c4224 --- /dev/null +++ b/benchmark/vm_call_kw_and_kw_splat.yml @@ -0,0 +1,25 @@ +prelude: | + h1, h10, h100, h1000 = [1, 10, 100, 1000].map do |n| + h = {kw: 1} + n.times{|i| h[i.to_s.to_sym] = i} + h + end + eh = {} + def kw(kw: nil, **kws) end +benchmark: + 1: | + kw(**h1) + 1_mutable: | + kw(**eh, **h1) + 10: | + kw(**h10) + 10_mutable: | + kw(**eh, **h10) + 100: | + kw(**h100) + 100_mutable: | + kw(**eh, **h100) + 1000: | + kw(**h1000) + 1000_mutable: | + kw(**eh, **h1000) diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 956cc9c56da8ec..06bba60ac5c98b 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -4002,6 +4002,20 @@ def m(a: []) }, bug8964 end + def test_large_kwsplat_to_method_taking_kw_and_kwsplat + assert_separately(['-'], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + n = 100000 + x = Fiber.new do + h = {kw: 2} + n.times{|i| h[i.to_s.to_sym] = i} + def self.f(kw: 1, **kws) kws.size end + f(**h) + end.resume + assert_equal(n, x) + end; + end + def test_dynamic_symbol_keyword bug10266 = '[ruby-dev:48564] [Bug #10266]' assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}") diff --git a/vm_args.c b/vm_args.c index aa8c2ec2993b72..28c0e339a36a7e 100644 --- a/vm_args.c +++ b/vm_args.c @@ -396,6 +396,83 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons locals[key_num] = unspecified_bits_value; } +static void +args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, + VALUE keyword_hash, VALUE *const locals) +{ + const ID *acceptable_keywords = ISEQ_BODY(iseq)->param.keyword->table; + const int req_key_num = ISEQ_BODY(iseq)->param.keyword->required_num; + const int key_num = ISEQ_BODY(iseq)->param.keyword->num; + const VALUE * const default_values = ISEQ_BODY(iseq)->param.keyword->default_values; + VALUE missing = 0; + int i, di; + int unspecified_bits = 0; + VALUE unspecified_bits_value = Qnil; + + for (i=0; i hash */ + int j; + unspecified_bits_value = rb_hash_new(); + + for (j=0; jparam.flags.has_kwrest) { + const int rest_hash_index = key_num + 1; + locals[rest_hash_index] = keyword_hash; + } + else { + if (!RHASH_EMPTY_P(keyword_hash)) { + argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); + } + } + + if (NIL_P(unspecified_bits_value)) { + unspecified_bits_value = INT2FIX(unspecified_bits); + } + locals[key_num] = unspecified_bits_value; +} + static inline void args_setup_kw_rest_parameter(VALUE keyword_hash, VALUE *locals, int kw_flag) { @@ -415,22 +492,6 @@ args_setup_block_parameter(const rb_execution_context_t *ec, struct rb_calling_i *locals = rb_vm_bh_to_procval(ec, block_handler); } -struct fill_values_arg { - VALUE *keys; - VALUE *vals; - int argc; -}; - -static int -fill_keys_values(st_data_t key, st_data_t val, st_data_t ptr) -{ - struct fill_values_arg *arg = (struct fill_values_arg *)ptr; - int i = arg->argc++; - arg->keys[i] = (VALUE)key; - arg->vals[i] = (VALUE)val; - return ST_CONTINUE; -} - static inline int ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned int * kw_flag, VALUE * converted_keyword_hash) { @@ -746,15 +807,8 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co args_setup_kw_parameters(ec, iseq, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); } else if (!NIL_P(keyword_hash)) { - int kw_len = rb_long2int(RHASH_SIZE(keyword_hash)); - struct fill_values_arg arg; - /* copy kw_argv */ - arg.keys = args->kw_argv = ALLOCA_N(VALUE, kw_len * 2); - arg.vals = arg.keys + kw_len; - arg.argc = 0; - rb_hash_foreach(keyword_hash, fill_keys_values, (VALUE)&arg); - VM_ASSERT(arg.argc == kw_len); - args_setup_kw_parameters(ec, iseq, arg.vals, kw_len, arg.keys, klocals); + keyword_hash = check_kwrestarg(keyword_hash, &kw_flag); + args_setup_kw_parameters_from_kwsplat(ec, iseq, keyword_hash, klocals); } else { VM_ASSERT(args_argc(args) == 0); From 4f7de1dcddf33b1ad4035a87eedbd8519ee790fd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 16:50:30 +0900 Subject: [PATCH 130/142] Adjust styles [ci skip] --- main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index d434f84c40f09f..cbb294ba724999 100644 --- a/main.c +++ b/main.c @@ -63,7 +63,9 @@ main(int argc, char **argv) /* Compile in the ASAN options Ruby needs, rather than relying on environment variables, so * that even tests which fork ruby with a clean environment will run ASAN with the right * settings */ -const char *__asan_default_options(void) { - return "use_sigaltstack=0:detect_leaks=0"; +const char * +__asan_default_options(void) +{ + return "use_sigaltstack=0:detect_leaks=0"; } #endif From 7fc89a92620f09df38b1f89d7dbd4d8b9f484aeb Mon Sep 17 00:00:00 2001 From: yui-knk Date: Mon, 12 Feb 2024 14:30:48 +0900 Subject: [PATCH 131/142] Use Node for `warn_duplicate_keys` st_table keys --- parse.y | 49 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/parse.y b/parse.y index 366afa3d206534..b261520cf53ddf 100644 --- a/parse.y +++ b/parse.y @@ -86,6 +86,7 @@ hash_literal_key_p(VALUE k) case NODE_FLOAT: case NODE_RATIONAL: case NODE_IMAGINARY: + case NODE_STR: case NODE_SYM: case NODE_LINE: case NODE_FILE: @@ -149,14 +150,6 @@ node_cdhash_cmp(VALUE val, VALUE lit) return 0; } - /* Special case for __FILE__ and String */ - if (OBJ_BUILTIN_TYPE(val) == T_NODE && nd_type(RNODE(val)) == NODE_FILE && RB_TYPE_P(lit, T_STRING)) { - return rb_str_hash_cmp(rb_node_file_path_val(RNODE(val)), lit); - } - if (OBJ_BUILTIN_TYPE(lit) == T_NODE && nd_type(RNODE(lit)) == NODE_FILE && RB_TYPE_P(val, T_STRING)) { - return rb_str_hash_cmp(rb_node_file_path_val(RNODE(lit)), val); - } - if ((OBJ_BUILTIN_TYPE(val) == T_NODE) && (OBJ_BUILTIN_TYPE(lit) == T_NODE)) { NODE *node_val = RNODE(val); NODE *node_lit = RNODE(lit); @@ -171,6 +164,14 @@ node_cdhash_cmp(VALUE val, VALUE lit) return node_integer_line_cmp(node_lit, node_val); } + /* Special case for String and __FILE__ */ + if (type_val == NODE_STR && type_lit == NODE_FILE) { + return rb_parser_string_hash_cmp(RNODE_STR(node_val)->string, RNODE_FILE(node_lit)->path); + } + if (type_lit == NODE_STR && type_val == NODE_FILE) { + return rb_parser_string_hash_cmp(RNODE_STR(node_lit)->string, RNODE_FILE(node_val)->path); + } + if (type_val != type_lit) { return -1; } @@ -184,6 +185,8 @@ node_cdhash_cmp(VALUE val, VALUE lit) return node_rational_cmp(RNODE_RATIONAL(node_val), RNODE_RATIONAL(node_lit)); case NODE_IMAGINARY: return node_imaginary_cmp(RNODE_IMAGINARY(node_val), RNODE_IMAGINARY(node_lit)); + case NODE_STR: + return rb_parser_string_hash_cmp(RNODE_STR(node_val)->string, RNODE_STR(node_lit)->string); case NODE_SYM: return rb_parser_string_hash_cmp(RNODE_SYM(node_val)->string, RNODE_SYM(node_lit)->string); case NODE_LINE: @@ -224,13 +227,15 @@ node_cdhash_hash(VALUE a) case NODE_IMAGINARY: val = rb_node_imaginary_literal_val(node); return rb_complex_hash(val); + case NODE_STR: + return rb_str_hash(rb_node_str_string_val(node)); case NODE_SYM: return rb_node_sym_string_val(node); case NODE_LINE: /* Same with NODE_INTEGER FIXNUM case */ return (st_index_t)node->nd_loc.beg_pos.lineno; case NODE_FILE: - /* Same with String in rb_iseq_cdhash_hash */ + /* Same with NODE_STR */ return rb_str_hash(rb_node_file_path_val(node)); case NODE_ENCODING: return rb_node_encoding_val(node); @@ -15462,8 +15467,30 @@ nd_type_st_key_enable_p(NODE *node) } } -static VALUE +static NODE * nd_st_key(struct parser_params *p, NODE *node) +{ + switch (nd_type(node)) { + case NODE_LIT: + return (NODE *)RNODE_LIT(node)->nd_lit; + case NODE_STR: + case NODE_INTEGER: + case NODE_FLOAT: + case NODE_RATIONAL: + case NODE_IMAGINARY: + case NODE_SYM: + case NODE_LINE: + case NODE_ENCODING: + case NODE_FILE: + return node; + default: + rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); + UNREACHABLE_RETURN(0); + } +} + +static VALUE +nd_value(struct parser_params *p, NODE *node) { switch (nd_type(node)) { case NODE_LIT: @@ -15514,7 +15541,7 @@ warn_duplicate_keys(struct parser_params *p, NODE *hash) st_delete(literal_keys, (key = (st_data_t)nd_st_key(p, head), &key), &data)) { rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data), "key %+"PRIsVALUE" is duplicated and overwritten on line %d", - nd_st_key(p, head), nd_line(head)); + nd_value(p, head), nd_line(head)); } st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); hash = next; From 90a746d246d51d105d7f3e0d1c2ddf7994dd2d4b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 10:55:57 +0100 Subject: [PATCH 132/142] Always extract bundled gems before running ruby/spec * Fixes https://github.com/ruby/ruby/commit/44d74f22c8da3c13aa5363769418e2f5fd29f65a#r138276491 --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 68ffdf40769e52..31a74d3ead6788 100644 --- a/common.mk +++ b/common.mk @@ -983,7 +983,7 @@ $(RBCONFIG): $(tooldir)/mkconfig.rb config.status $(srcdir)/version.h $(srcdir)/ test-rubyspec: test-spec yes-test-rubyspec: yes-test-spec -yes-test-spec-precheck: yes-test-all-precheck yes-fake +yes-test-spec-precheck: yes-test-all-precheck yes-fake extract-gems test-spec: $(TEST_RUNNABLE)-test-spec yes-test-spec: yes-test-spec-precheck From b19d2409be0b7bac65e6e54cfb3bedc61419ec01 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 10:56:33 +0100 Subject: [PATCH 133/142] Update to ruby/spec@ce834ad --- spec/ruby/core/integer/coerce_spec.rb | 19 +- spec/ruby/core/module/prepend_spec.rb | 33 ++ spec/ruby/core/rational/coerce_spec.rb | 1 - spec/ruby/core/time/at_spec.rb | 10 +- spec/ruby/library/abbrev/abbrev_spec.rb | 47 +- spec/ruby/library/base64/decode64_spec.rb | 41 +- spec/ruby/library/base64/encode64_spec.rb | 33 +- .../library/base64/strict_decode64_spec.rb | 77 ++-- .../library/base64/strict_encode64_spec.rb | 27 +- .../library/base64/urlsafe_decode64_spec.rb | 27 +- .../library/base64/urlsafe_encode64_spec.rb | 29 +- .../library/bigdecimal/BigDecimal_spec.rb | 415 +++++++++-------- spec/ruby/library/bigdecimal/abs_spec.rb | 89 ++-- spec/ruby/library/bigdecimal/add_spec.rb | 339 +++++++------- .../library/bigdecimal/case_compare_spec.rb | 9 +- spec/ruby/library/bigdecimal/ceil_spec.rb | 187 ++++---- spec/ruby/library/bigdecimal/clone_spec.rb | 9 +- spec/ruby/library/bigdecimal/coerce_spec.rb | 43 +- .../library/bigdecimal/comparison_spec.rb | 137 +++--- .../ruby/library/bigdecimal/constants_spec.rb | 111 +++-- spec/ruby/library/bigdecimal/div_spec.rb | 185 ++++---- spec/ruby/library/bigdecimal/divide_spec.rb | 23 +- spec/ruby/library/bigdecimal/divmod_spec.rb | 293 ++++++------ .../library/bigdecimal/double_fig_spec.rb | 13 +- spec/ruby/library/bigdecimal/dup_spec.rb | 9 +- spec/ruby/library/bigdecimal/eql_spec.rb | 9 +- .../library/bigdecimal/equal_value_spec.rb | 9 +- spec/ruby/library/bigdecimal/exponent_spec.rb | 41 +- spec/ruby/library/bigdecimal/finite_spec.rb | 57 ++- spec/ruby/library/bigdecimal/fix_spec.rb | 99 ++-- spec/ruby/library/bigdecimal/floor_spec.rb | 169 ++++--- spec/ruby/library/bigdecimal/frac_spec.rb | 89 ++-- spec/ruby/library/bigdecimal/gt_spec.rb | 161 ++++--- spec/ruby/library/bigdecimal/gte_spec.rb | 169 ++++--- spec/ruby/library/bigdecimal/hash_spec.rb | 43 +- spec/ruby/library/bigdecimal/infinite_spec.rb | 51 +-- spec/ruby/library/bigdecimal/inspect_spec.rb | 45 +- spec/ruby/library/bigdecimal/limit_spec.rb | 89 ++-- spec/ruby/library/bigdecimal/lt_spec.rb | 161 ++++--- spec/ruby/library/bigdecimal/lte_spec.rb | 169 ++++--- spec/ruby/library/bigdecimal/minus_spec.rb | 109 +++-- spec/ruby/library/bigdecimal/mode_spec.rb | 61 ++- spec/ruby/library/bigdecimal/modulo_spec.rb | 19 +- spec/ruby/library/bigdecimal/mult_spec.rb | 49 +- spec/ruby/library/bigdecimal/multiply_spec.rb | 63 ++- spec/ruby/library/bigdecimal/nan_spec.rb | 37 +- spec/ruby/library/bigdecimal/nonzero_spec.rb | 49 +- spec/ruby/library/bigdecimal/plus_spec.rb | 93 ++-- spec/ruby/library/bigdecimal/power_spec.rb | 9 +- spec/ruby/library/bigdecimal/precs_spec.rb | 79 ++-- spec/ruby/library/bigdecimal/quo_spec.rb | 17 +- .../ruby/library/bigdecimal/remainder_spec.rb | 161 ++++--- spec/ruby/library/bigdecimal/round_spec.rb | 423 +++++++++--------- spec/ruby/library/bigdecimal/sign_spec.rb | 85 ++-- spec/ruby/library/bigdecimal/split_spec.rb | 149 +++--- spec/ruby/library/bigdecimal/sqrt_spec.rb | 217 +++++---- spec/ruby/library/bigdecimal/sub_spec.rb | 115 +++-- spec/ruby/library/bigdecimal/to_d_spec.rb | 15 +- spec/ruby/library/bigdecimal/to_f_spec.rb | 103 +++-- spec/ruby/library/bigdecimal/to_i_spec.rb | 11 +- spec/ruby/library/bigdecimal/to_int_spec.rb | 11 +- spec/ruby/library/bigdecimal/to_r_spec.rb | 43 +- spec/ruby/library/bigdecimal/to_s_spec.rb | 159 ++++--- spec/ruby/library/bigdecimal/truncate_spec.rb | 135 +++--- spec/ruby/library/bigdecimal/uminus_spec.rb | 101 ++--- spec/ruby/library/bigdecimal/uplus_spec.rb | 29 +- spec/ruby/library/bigdecimal/util_spec.rb | 59 ++- spec/ruby/library/bigdecimal/zero_spec.rb | 45 +- spec/ruby/library/bigmath/log_spec.rb | 15 +- .../basicwriter/close_on_terminate_spec.rb | 9 +- .../csv/basicwriter/initialize_spec.rb | 9 +- .../library/csv/basicwriter/terminate_spec.rb | 9 +- spec/ruby/library/csv/cell/data_spec.rb | 9 +- spec/ruby/library/csv/cell/initialize_spec.rb | 9 +- spec/ruby/library/csv/foreach_spec.rb | 9 +- spec/ruby/library/csv/generate_line_spec.rb | 45 +- spec/ruby/library/csv/generate_row_spec.rb | 9 +- spec/ruby/library/csv/generate_spec.rb | 47 +- spec/ruby/library/csv/iobuf/close_spec.rb | 9 +- .../ruby/library/csv/iobuf/initialize_spec.rb | 9 +- spec/ruby/library/csv/iobuf/read_spec.rb | 9 +- spec/ruby/library/csv/iobuf/terminate_spec.rb | 9 +- .../csv/ioreader/close_on_terminate_spec.rb | 9 +- .../ruby/library/csv/ioreader/get_row_spec.rb | 9 +- .../library/csv/ioreader/initialize_spec.rb | 9 +- .../library/csv/ioreader/terminate_spec.rb | 9 +- spec/ruby/library/csv/liberal_parsing_spec.rb | 29 +- spec/ruby/library/csv/open_spec.rb | 9 +- spec/ruby/library/csv/parse_spec.rb | 181 ++++---- spec/ruby/library/csv/read_spec.rb | 9 +- spec/ruby/library/csv/readlines_spec.rb | 53 ++- .../library/csv/streambuf/add_buf_spec.rb | 9 +- .../library/csv/streambuf/buf_size_spec.rb | 9 +- spec/ruby/library/csv/streambuf/drop_spec.rb | 9 +- .../csv/streambuf/element_reference_spec.rb | 9 +- spec/ruby/library/csv/streambuf/get_spec.rb | 9 +- .../library/csv/streambuf/idx_is_eos_spec.rb | 9 +- .../library/csv/streambuf/initialize_spec.rb | 9 +- .../ruby/library/csv/streambuf/is_eos_spec.rb | 9 +- spec/ruby/library/csv/streambuf/read_spec.rb | 9 +- .../library/csv/streambuf/rel_buf_spec.rb | 9 +- .../library/csv/streambuf/terminate_spec.rb | 9 +- .../library/csv/stringreader/get_row_spec.rb | 9 +- .../csv/stringreader/initialize_spec.rb | 9 +- spec/ruby/library/csv/writer/add_row_spec.rb | 9 +- spec/ruby/library/csv/writer/append_spec.rb | 9 +- spec/ruby/library/csv/writer/close_spec.rb | 9 +- spec/ruby/library/csv/writer/create_spec.rb | 9 +- spec/ruby/library/csv/writer/generate_spec.rb | 9 +- .../library/csv/writer/initialize_spec.rb | 9 +- .../ruby/library/csv/writer/terminate_spec.rb | 9 +- spec/ruby/library/drb/start_service_spec.rb | 45 +- .../library/getoptlong/each_option_spec.rb | 11 +- spec/ruby/library/getoptlong/each_spec.rb | 11 +- .../library/getoptlong/error_message_spec.rb | 35 +- .../library/getoptlong/get_option_spec.rb | 11 +- spec/ruby/library/getoptlong/get_spec.rb | 11 +- .../library/getoptlong/initialize_spec.rb | 41 +- spec/ruby/library/getoptlong/ordering_spec.rb | 57 ++- .../library/getoptlong/set_options_spec.rb | 151 +++---- .../ruby/library/getoptlong/terminate_spec.rb | 47 +- .../library/getoptlong/terminated_spec.rb | 23 +- .../library/observer/add_observer_spec.rb | 35 +- .../library/observer/count_observers_spec.rb | 37 +- .../library/observer/delete_observer_spec.rb | 29 +- .../library/observer/delete_observers_spec.rb | 29 +- .../ruby/library/observer/fixtures/classes.rb | 24 +- .../library/observer/notify_observers_spec.rb | 49 +- spec/ruby/library/syslog/alert_spec.rb | 13 +- spec/ruby/library/syslog/close_spec.rb | 107 +++-- spec/ruby/library/syslog/constants_spec.rb | 57 ++- spec/ruby/library/syslog/crit_spec.rb | 13 +- spec/ruby/library/syslog/debug_spec.rb | 13 +- spec/ruby/library/syslog/emerg_spec.rb | 23 +- spec/ruby/library/syslog/err_spec.rb | 13 +- spec/ruby/library/syslog/facility_spec.rb | 87 ++-- spec/ruby/library/syslog/ident_spec.rb | 49 +- spec/ruby/library/syslog/info_spec.rb | 13 +- spec/ruby/library/syslog/inspect_spec.rb | 57 ++- spec/ruby/library/syslog/instance_spec.rb | 15 +- spec/ruby/library/syslog/log_spec.rb | 85 ++-- spec/ruby/library/syslog/mask_spec.rb | 151 +++---- spec/ruby/library/syslog/notice_spec.rb | 13 +- spec/ruby/library/syslog/open_spec.rb | 141 +++--- spec/ruby/library/syslog/opened_spec.rb | 55 ++- spec/ruby/library/syslog/options_spec.rb | 87 ++-- spec/ruby/library/syslog/reopen_spec.rb | 13 +- spec/ruby/library/syslog/shared/log.rb | 66 ++- spec/ruby/library/syslog/shared/reopen.rb | 64 ++- spec/ruby/library/syslog/warning_spec.rb | 13 +- spec/ruby/optional/capi/ext/util_spec.c | 17 +- spec/ruby/shared/rational/coerce.rb | 13 +- 152 files changed, 4064 insertions(+), 4476 deletions(-) diff --git a/spec/ruby/core/integer/coerce_spec.rb b/spec/ruby/core/integer/coerce_spec.rb index 13fe7b332177e7..f1f32560323924 100644 --- a/spec/ruby/core/integer/coerce_spec.rb +++ b/spec/ruby/core/integer/coerce_spec.rb @@ -1,5 +1,7 @@ require_relative '../../spec_helper' +require 'bigdecimal' + describe "Integer#coerce" do context "fixnum" do describe "when given a Fixnum" do @@ -89,16 +91,13 @@ end end - ruby_version_is ""..."3.4" do - require 'bigdecimal' - context "bigdecimal" do - it "produces Floats" do - x, y = 3.coerce(BigDecimal("3.4")) - x.class.should == Float - x.should == 3.4 - y.class.should == Float - y.should == 3.0 - end + context "bigdecimal" do + it "produces Floats" do + x, y = 3.coerce(BigDecimal("3.4")) + x.class.should == Float + x.should == 3.4 + y.class.should == Float + y.should == 3.0 end end diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb index f80cfbcca18b2a..c90fa9700ec3b4 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -737,6 +737,39 @@ def foo(ary) klass.ancestors.take(4).should == [klass, submod, mod, Object] end + # https://bugs.ruby-lang.org/issues/17423 + describe "when module already exists in ancestor chain" do + ruby_version_is ""..."3.1" do + it "does not modify the ancestor chain" do + m = Module.new do; end + a = Module.new do; end + b = Class.new do; end + + b.include(a) + a.prepend(m) + b.ancestors.take(4).should == [b, m, a, Object] + + b.prepend(m) + b.ancestors.take(4).should == [b, m, a, Object] + end + end + + ruby_version_is "3.1" do + it "modifies the ancestor chain" do + m = Module.new do; end + a = Module.new do; end + b = Class.new do; end + + b.include(a) + a.prepend(m) + b.ancestors.take(4).should == [b, m, a, Object] + + b.prepend(m) + b.ancestors.take(5).should == [m, b, m, a, Object] + end + end + end + describe "called on a module" do describe "included into a class" it "does not obscure the module's methods from reflective access" do diff --git a/spec/ruby/core/rational/coerce_spec.rb b/spec/ruby/core/rational/coerce_spec.rb index 7aea31aea9eecb..9c0f05829b42e5 100644 --- a/spec/ruby/core/rational/coerce_spec.rb +++ b/spec/ruby/core/rational/coerce_spec.rb @@ -1,5 +1,4 @@ require_relative "../../spec_helper" - require_relative '../../shared/rational/coerce' describe "Rational#coerce" do diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 25056f20115722..0459589f0192a9 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -32,12 +32,10 @@ t2.nsec.should == t.nsec end - ruby_version_is ""..."3.4" do - describe "passed BigDecimal" do - it "doesn't round input value" do - require 'bigdecimal' - Time.at(BigDecimal('1.1')).to_f.should == 1.1 - end + describe "passed BigDecimal" do + it "doesn't round input value" do + require 'bigdecimal' + Time.at(BigDecimal('1.1')).to_f.should == 1.1 end end diff --git a/spec/ruby/library/abbrev/abbrev_spec.rb b/spec/ruby/library/abbrev/abbrev_spec.rb index 79045a080451ed..61b09265977eec 100644 --- a/spec/ruby/library/abbrev/abbrev_spec.rb +++ b/spec/ruby/library/abbrev/abbrev_spec.rb @@ -1,34 +1,31 @@ require_relative '../../spec_helper' +require 'abbrev' -ruby_version_is ""..."3.4" do - require 'abbrev' +#test both Abbrev.abbrev and Array#abbrev in +#the same manner, as they're more or less aliases +#of one another - #test both Abbrev.abbrev and Array#abbrev in - #the same manner, as they're more or less aliases - #of one another +[["Abbrev.abbrev", -> a { Abbrev.abbrev(a)}], + ["Array#abbrev", -> a { a.abbrev}] +].each do |(name, func)| - [["Abbrev.abbrev", -> a { Abbrev.abbrev(a)}], - ["Array#abbrev", -> a { a.abbrev}] - ].each do |(name, func)| + describe name do + it "returns a hash of all unambiguous abbreviations of the array of strings passed in" do + func.call(['ruby', 'rules']).should == {"rub" => "ruby", + "ruby" => "ruby", + "rul" => "rules", + "rule" => "rules", + "rules" => "rules"} - describe name do - it "returns a hash of all unambiguous abbreviations of the array of strings passed in" do - func.call(['ruby', 'rules']).should == {"rub" => "ruby", - "ruby" => "ruby", - "rul" => "rules", - "rule" => "rules", - "rules" => "rules"} - - func.call(["car", "cone"]).should == {"ca" => "car", - "car" => "car", - "co" => "cone", - "con" => "cone", - "cone" => "cone"} - end + func.call(["car", "cone"]).should == {"ca" => "car", + "car" => "car", + "co" => "cone", + "con" => "cone", + "cone" => "cone"} + end - it "returns an empty hash when called on an empty array" do - func.call([]).should == {} - end + it "returns an empty hash when called on an empty array" do + func.call([]).should == {} end end end diff --git a/spec/ruby/library/base64/decode64_spec.rb b/spec/ruby/library/base64/decode64_spec.rb index c587c82d28f088..6dd33dddfe2e2a 100644 --- a/spec/ruby/library/base64/decode64_spec.rb +++ b/spec/ruby/library/base64/decode64_spec.rb @@ -1,32 +1,29 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +require 'base64' - require 'base64' - - describe "Base64#decode64" do - it "returns the Base64-decoded version of the given string" do - Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n").should == "Send reinforcements" - end +describe "Base64#decode64" do + it "returns the Base64-decoded version of the given string" do + Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n").should == "Send reinforcements" + end - it "returns the Base64-decoded version of the given shared string" do - Base64.decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==\n".split(" ").last).should == "Send reinforcements" - end + it "returns the Base64-decoded version of the given shared string" do + Base64.decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==\n".split(" ").last).should == "Send reinforcements" + end - it "returns the Base64-decoded version of the given string with wrong padding" do - Base64.decode64("XU2VuZCByZWluZm9yY2VtZW50cw===").should == "]M\x95\xB9\x90\x81\xC9\x95\xA5\xB9\x99\xBD\xC9\x8D\x95\xB5\x95\xB9\xD1\xCC".b - end + it "returns the Base64-decoded version of the given string with wrong padding" do + Base64.decode64("XU2VuZCByZWluZm9yY2VtZW50cw===").should == "]M\x95\xB9\x90\x81\xC9\x95\xA5\xB9\x99\xBD\xC9\x8D\x95\xB5\x95\xB9\xD1\xCC".b + end - it "returns the Base64-decoded version of the given string that contains an invalid character" do - Base64.decode64("%3D").should == "\xDC".b - end + it "returns the Base64-decoded version of the given string that contains an invalid character" do + Base64.decode64("%3D").should == "\xDC".b + end - it "returns a binary encoded string" do - Base64.decode64("SEk=").encoding.should == Encoding::BINARY - end + it "returns a binary encoded string" do + Base64.decode64("SEk=").encoding.should == Encoding::BINARY + end - it "decodes without padding suffix ==" do - Base64.decode64("eyJrZXkiOnsibiI6InR0dCJ9fQ").should == "{\"key\":{\"n\":\"ttt\"}}" - end + it "decodes without padding suffix ==" do + Base64.decode64("eyJrZXkiOnsibiI6InR0dCJ9fQ").should == "{\"key\":{\"n\":\"ttt\"}}" end end diff --git a/spec/ruby/library/base64/encode64_spec.rb b/spec/ruby/library/base64/encode64_spec.rb index e31c6604f0a347..64de6257bc86ed 100644 --- a/spec/ruby/library/base64/encode64_spec.rb +++ b/spec/ruby/library/base64/encode64_spec.rb @@ -1,26 +1,23 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +require 'base64' - require 'base64' - - describe "Base64#encode64" do - it "returns the Base64-encoded version of the given string" do - Base64.encode64("Now is the time for all good coders\nto learn Ruby").should == - "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n" - end +describe "Base64#encode64" do + it "returns the Base64-encoded version of the given string" do + Base64.encode64("Now is the time for all good coders\nto learn Ruby").should == + "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n" + end - it "returns the Base64-encoded version of the given string" do - Base64.encode64('Send reinforcements').should == "U2VuZCByZWluZm9yY2VtZW50cw==\n" - end + it "returns the Base64-encoded version of the given string" do + Base64.encode64('Send reinforcements').should == "U2VuZCByZWluZm9yY2VtZW50cw==\n" + end - it "returns the Base64-encoded version of the given shared string" do - Base64.encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == - "dG8gbGVhcm4gUnVieQ==\n" - end + it "returns the Base64-encoded version of the given shared string" do + Base64.encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == + "dG8gbGVhcm4gUnVieQ==\n" + end - it "returns a US_ASCII encoded string" do - Base64.encode64("HI").encoding.should == Encoding::US_ASCII - end + it "returns a US_ASCII encoded string" do + Base64.encode64("HI").encoding.should == Encoding::US_ASCII end end diff --git a/spec/ruby/library/base64/strict_decode64_spec.rb b/spec/ruby/library/base64/strict_decode64_spec.rb index f4dd3a60fe979c..d258223c827277 100644 --- a/spec/ruby/library/base64/strict_decode64_spec.rb +++ b/spec/ruby/library/base64/strict_decode64_spec.rb @@ -1,44 +1,41 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - require 'base64' - - describe "Base64#strict_decode64" do - it "returns the Base64-decoded version of the given string" do - Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==").should == "Send reinforcements" - end - - it "returns the Base64-decoded version of the given shared string" do - Base64.strict_decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==".split(" ").last).should == "Send reinforcements" - end - - it "raises ArgumentError when the given string contains CR" do - -> do - Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\r") - end.should raise_error(ArgumentError) - end - - it "raises ArgumentError when the given string contains LF" do - -> do - Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n") - end.should raise_error(ArgumentError) - end - - it "raises ArgumentError when the given string has wrong padding" do - -> do - Base64.strict_decode64("=U2VuZCByZWluZm9yY2VtZW50cw==") - end.should raise_error(ArgumentError) - end - - it "raises ArgumentError when the given string contains an invalid character" do - -> do - Base64.strict_decode64("%3D") - end.should raise_error(ArgumentError) - end - - it "returns a binary encoded string" do - Base64.strict_decode64("SEk=").encoding.should == Encoding::BINARY - end +require 'base64' + +describe "Base64#strict_decode64" do + it "returns the Base64-decoded version of the given string" do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==").should == "Send reinforcements" + end + + it "returns the Base64-decoded version of the given shared string" do + Base64.strict_decode64("base64: U2VuZCByZWluZm9yY2VtZW50cw==".split(" ").last).should == "Send reinforcements" + end + + it "raises ArgumentError when the given string contains CR" do + -> do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\r") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string contains LF" do + -> do + Base64.strict_decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string has wrong padding" do + -> do + Base64.strict_decode64("=U2VuZCByZWluZm9yY2VtZW50cw==") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the given string contains an invalid character" do + -> do + Base64.strict_decode64("%3D") + end.should raise_error(ArgumentError) + end + + it "returns a binary encoded string" do + Base64.strict_decode64("SEk=").encoding.should == Encoding::BINARY end end diff --git a/spec/ruby/library/base64/strict_encode64_spec.rb b/spec/ruby/library/base64/strict_encode64_spec.rb index c7f9f12a9692fd..7cabcf190cc100 100644 --- a/spec/ruby/library/base64/strict_encode64_spec.rb +++ b/spec/ruby/library/base64/strict_encode64_spec.rb @@ -1,22 +1,19 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +require 'base64' - require 'base64' - - describe "Base64#strict_encode64" do - it "returns the Base64-encoded version of the given string" do - Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby").should == - "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ==" - end +describe "Base64#strict_encode64" do + it "returns the Base64-encoded version of the given string" do + Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby").should == + "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ==" + end - it "returns the Base64-encoded version of the given shared string" do - Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == - "dG8gbGVhcm4gUnVieQ==" - end + it "returns the Base64-encoded version of the given shared string" do + Base64.strict_encode64("Now is the time for all good coders\nto learn Ruby".split("\n").last).should == + "dG8gbGVhcm4gUnVieQ==" + end - it "returns a US_ASCII encoded string" do - Base64.strict_encode64("HI").encoding.should == Encoding::US_ASCII - end + it "returns a US_ASCII encoded string" do + Base64.strict_encode64("HI").encoding.should == Encoding::US_ASCII end end diff --git a/spec/ruby/library/base64/urlsafe_decode64_spec.rb b/spec/ruby/library/base64/urlsafe_decode64_spec.rb index f9a46787f71758..1b813ee1b943da 100644 --- a/spec/ruby/library/base64/urlsafe_decode64_spec.rb +++ b/spec/ruby/library/base64/urlsafe_decode64_spec.rb @@ -1,22 +1,19 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +require 'base64' - require 'base64' - - describe "Base64#urlsafe_decode64" do - it "uses '_' instead of '/'" do - decoded = Base64.urlsafe_decode64("V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_") - decoded.should == 'Where am I? Who am I? Am I? I?' - end +describe "Base64#urlsafe_decode64" do + it "uses '_' instead of '/'" do + decoded = Base64.urlsafe_decode64("V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_") + decoded.should == 'Where am I? Who am I? Am I? I?' + end - it "uses '-' instead of '+'" do - decoded = Base64.urlsafe_decode64('IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-') - decoded.should == '"Being disintegrated makes me ve-ry an-gry!" ' - end + it "uses '-' instead of '+'" do + decoded = Base64.urlsafe_decode64('IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-') + decoded.should == '"Being disintegrated makes me ve-ry an-gry!" ' + end - it "does not require padding" do - Base64.urlsafe_decode64("MQ").should == "1" - end + it "does not require padding" do + Base64.urlsafe_decode64("MQ").should == "1" end end diff --git a/spec/ruby/library/base64/urlsafe_encode64_spec.rb b/spec/ruby/library/base64/urlsafe_encode64_spec.rb index 0c7b08757eef3b..de1f235ceab035 100644 --- a/spec/ruby/library/base64/urlsafe_encode64_spec.rb +++ b/spec/ruby/library/base64/urlsafe_encode64_spec.rb @@ -1,23 +1,20 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +require 'base64' - require 'base64' - - describe "Base64#urlsafe_encode64" do - it "uses '_' instead of '/'" do - encoded = Base64.urlsafe_encode64('Where am I? Who am I? Am I? I?') - encoded.should == "V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_" - end +describe "Base64#urlsafe_encode64" do + it "uses '_' instead of '/'" do + encoded = Base64.urlsafe_encode64('Where am I? Who am I? Am I? I?') + encoded.should == "V2hlcmUgYW0gST8gV2hvIGFtIEk_IEFtIEk_IEk_" + end - it "uses '-' instead of '+'" do - encoded = Base64.urlsafe_encode64('"Being disintegrated makes me ve-ry an-gry!" ') - encoded.should == 'IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-' - end + it "uses '-' instead of '+'" do + encoded = Base64.urlsafe_encode64('"Being disintegrated makes me ve-ry an-gry!" ') + encoded.should == 'IkJlaW5nIGRpc2ludGVncmF0ZWQgbWFrZXMgbWUgdmUtcnkgYW4tZ3J5ISIgPGh1ZmYsIGh1ZmY-' + end - it "makes padding optional" do - Base64.urlsafe_encode64("1", padding: false).should == "MQ" - Base64.urlsafe_encode64("1").should == "MQ==" - end + it "makes padding optional" do + Base64.urlsafe_encode64("1", padding: false).should == "MQ" + Base64.urlsafe_encode64("1").should == "MQ==" end end diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb index d6e119b36ea137..43a779b420ea35 100644 --- a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb +++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb @@ -1,272 +1,269 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal" do + it "is not defined unless it is required" do + ruby_exe('puts Object.const_defined?(:BigDecimal)').should == "false\n" + end +end - describe "BigDecimal" do - it "is not defined unless it is required" do - ruby_exe('puts Object.const_defined?(:BigDecimal)').should == "false\n" - end +describe "Kernel#BigDecimal" do + + it "creates a new object of class BigDecimal" do + BigDecimal("3.14159").should be_kind_of(BigDecimal) + (0..9).each {|i| + BigDecimal("1#{i}").should == 10 + i + BigDecimal("-1#{i}").should == -10 - i + BigDecimal("1E#{i}").should == 10**i + BigDecimal("1000000E-#{i}").should == 10**(6-i).to_f + # ^ to_f to avoid Rational type + } + (1..9).each {|i| + BigDecimal("100.#{i}").to_s.should =~ /\A0\.100#{i}E3\z/i + BigDecimal("-100.#{i}").to_s.should =~ /\A-0\.100#{i}E3\z/i + } end - describe "Kernel#BigDecimal" do - - it "creates a new object of class BigDecimal" do - BigDecimal("3.14159").should be_kind_of(BigDecimal) - (0..9).each {|i| - BigDecimal("1#{i}").should == 10 + i - BigDecimal("-1#{i}").should == -10 - i - BigDecimal("1E#{i}").should == 10**i - BigDecimal("1000000E-#{i}").should == 10**(6-i).to_f - # ^ to_f to avoid Rational type - } - (1..9).each {|i| - BigDecimal("100.#{i}").to_s.should =~ /\A0\.100#{i}E3\z/i - BigDecimal("-100.#{i}").to_s.should =~ /\A-0\.100#{i}E3\z/i - } - end + it "BigDecimal(Rational) with bigger-than-double numerator" do + rational = 99999999999999999999/100r + rational.numerator.should > 2**64 + BigDecimal(rational, 100).to_s.should == "0.99999999999999999999e18" + end - it "BigDecimal(Rational) with bigger-than-double numerator" do - rational = 99999999999999999999/100r - rational.numerator.should > 2**64 - BigDecimal(rational, 100).to_s.should == "0.99999999999999999999e18" + it "accepts significant digits >= given precision" do + suppress_warning do + BigDecimal("3.1415923", 10).precs[1].should >= 10 end + end - it "accepts significant digits >= given precision" do - suppress_warning do - BigDecimal("3.1415923", 10).precs[1].should >= 10 - end - end + it "determines precision from initial value" do + pi_string = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" + suppress_warning { + BigDecimal(pi_string).precs[1] + }.should >= pi_string.size-1 + end - it "determines precision from initial value" do - pi_string = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" - suppress_warning { - BigDecimal(pi_string).precs[1] - }.should >= pi_string.size-1 - end + it "ignores leading and trailing whitespace" do + BigDecimal(" \t\n \r1234\t\r\n ").should == BigDecimal("1234") + BigDecimal(" \t\n \rNaN \n").should.nan? + BigDecimal(" \t\n \rInfinity \n").infinite?.should == 1 + BigDecimal(" \t\n \r-Infinity \n").infinite?.should == -1 + end - it "ignores leading and trailing whitespace" do - BigDecimal(" \t\n \r1234\t\r\n ").should == BigDecimal("1234") - BigDecimal(" \t\n \rNaN \n").should.nan? - BigDecimal(" \t\n \rInfinity \n").infinite?.should == 1 - BigDecimal(" \t\n \r-Infinity \n").infinite?.should == -1 - end + it "coerces the value argument with #to_str" do + initial = mock("value") + initial.should_receive(:to_str).and_return("123") + BigDecimal(initial).should == BigDecimal("123") + end - it "coerces the value argument with #to_str" do - initial = mock("value") - initial.should_receive(:to_str).and_return("123") - BigDecimal(initial).should == BigDecimal("123") - end + it "does not ignores trailing garbage" do + -> { BigDecimal("123E45ruby") }.should raise_error(ArgumentError) + -> { BigDecimal("123x45") }.should raise_error(ArgumentError) + -> { BigDecimal("123.4%E5") }.should raise_error(ArgumentError) + -> { BigDecimal("1E2E3E4E5E") }.should raise_error(ArgumentError) + end - it "does not ignores trailing garbage" do - -> { BigDecimal("123E45ruby") }.should raise_error(ArgumentError) - -> { BigDecimal("123x45") }.should raise_error(ArgumentError) - -> { BigDecimal("123.4%E5") }.should raise_error(ArgumentError) - -> { BigDecimal("1E2E3E4E5E") }.should raise_error(ArgumentError) - end + it "raises ArgumentError for invalid strings" do + -> { BigDecimal("ruby") }.should raise_error(ArgumentError) + -> { BigDecimal(" \t\n \r-\t\t\tInfinity \n") }.should raise_error(ArgumentError) + end - it "raises ArgumentError for invalid strings" do - -> { BigDecimal("ruby") }.should raise_error(ArgumentError) - -> { BigDecimal(" \t\n \r-\t\t\tInfinity \n") }.should raise_error(ArgumentError) - end + it "allows omitting the integer part" do + BigDecimal(".123").should == BigDecimal("0.123") + end - it "allows omitting the integer part" do - BigDecimal(".123").should == BigDecimal("0.123") - end + it "process underscores as Float()" do + reference = BigDecimal("12345.67E89") + + BigDecimal("12_345.67E89").should == reference + -> { BigDecimal("1_2_3_4_5_._6____7_E89") }.should raise_error(ArgumentError) + -> { BigDecimal("12345_.67E_8__9_") }.should raise_error(ArgumentError) + end + + it "accepts NaN and [+-]Infinity" do + BigDecimal("NaN").should.nan? - it "process underscores as Float()" do - reference = BigDecimal("12345.67E89") + pos_inf = BigDecimal("Infinity") + pos_inf.should_not.finite? + pos_inf.should > 0 + pos_inf.should == BigDecimal("+Infinity") - BigDecimal("12_345.67E89").should == reference - -> { BigDecimal("1_2_3_4_5_._6____7_E89") }.should raise_error(ArgumentError) - -> { BigDecimal("12345_.67E_8__9_") }.should raise_error(ArgumentError) + neg_inf = BigDecimal("-Infinity") + neg_inf.should_not.finite? + neg_inf.should < 0 + end + + describe "with exception: false" do + it "returns nil for invalid strings" do + BigDecimal("invalid", exception: false).should be_nil + BigDecimal("0invalid", exception: false).should be_nil + BigDecimal("invalid0", exception: false).should be_nil + BigDecimal("0.", exception: false).should be_nil end + end - it "accepts NaN and [+-]Infinity" do - BigDecimal("NaN").should.nan? + describe "accepts NaN and [+-]Infinity as Float values" do + it "works without an explicit precision" do + BigDecimal(Float::NAN).should.nan? - pos_inf = BigDecimal("Infinity") + pos_inf = BigDecimal(Float::INFINITY) pos_inf.should_not.finite? pos_inf.should > 0 pos_inf.should == BigDecimal("+Infinity") - neg_inf = BigDecimal("-Infinity") + neg_inf = BigDecimal(-Float::INFINITY) neg_inf.should_not.finite? neg_inf.should < 0 end - describe "with exception: false" do - it "returns nil for invalid strings" do - BigDecimal("invalid", exception: false).should be_nil - BigDecimal("0invalid", exception: false).should be_nil - BigDecimal("invalid0", exception: false).should be_nil - BigDecimal("0.", exception: false).should be_nil - end + it "works with an explicit precision" do + BigDecimal(Float::NAN, Float::DIG).should.nan? + + pos_inf = BigDecimal(Float::INFINITY, Float::DIG) + pos_inf.should_not.finite? + pos_inf.should > 0 + pos_inf.should == BigDecimal("+Infinity") + + neg_inf = BigDecimal(-Float::INFINITY, Float::DIG) + neg_inf.should_not.finite? + neg_inf.should < 0 end + end + + it "allows for [eEdD] as exponent separator" do + reference = BigDecimal("12345.67E89") - describe "accepts NaN and [+-]Infinity as Float values" do - it "works without an explicit precision" do - BigDecimal(Float::NAN).should.nan? + BigDecimal("12345.67e89").should == reference + BigDecimal("12345.67E89").should == reference + BigDecimal("12345.67d89").should == reference + BigDecimal("12345.67D89").should == reference + end - pos_inf = BigDecimal(Float::INFINITY) - pos_inf.should_not.finite? - pos_inf.should > 0 - pos_inf.should == BigDecimal("+Infinity") + it "allows for varying signs" do + reference = BigDecimal("123.456E1") + + BigDecimal("+123.456E1").should == reference + BigDecimal("-123.456E1").should == -reference + BigDecimal("123.456E+1").should == reference + BigDecimal("12345.6E-1").should == reference + BigDecimal("+123.456E+1").should == reference + BigDecimal("+12345.6E-1").should == reference + BigDecimal("-123.456E+1").should == -reference + BigDecimal("-12345.6E-1").should == -reference + end - neg_inf = BigDecimal(-Float::INFINITY) - neg_inf.should_not.finite? - neg_inf.should < 0 - end + it "raises ArgumentError when Float is used without precision" do + -> { BigDecimal(1.0) }.should raise_error(ArgumentError) + end - it "works with an explicit precision" do - BigDecimal(Float::NAN, Float::DIG).should.nan? + it "returns appropriate BigDecimal zero for signed zero" do + BigDecimal(-0.0, Float::DIG).sign.should == -1 + BigDecimal(0.0, Float::DIG).sign.should == 1 + end - pos_inf = BigDecimal(Float::INFINITY, Float::DIG) - pos_inf.should_not.finite? - pos_inf.should > 0 - pos_inf.should == BigDecimal("+Infinity") + it "pre-coerces long integers" do + BigDecimal(3).add(1 << 50, 3).should == BigDecimal('0.113e16') + end + + it "does not call to_s when calling inspect" do + value = BigDecimal('44.44') + value.to_s.should == '0.4444e2' + value.inspect.should == '0.4444e2' - neg_inf = BigDecimal(-Float::INFINITY, Float::DIG) - neg_inf.should_not.finite? - neg_inf.should < 0 + ruby_exe( <<-'EOF').should == "cheese 0.4444e2" + require 'bigdecimal' + module BigDecimalOverride + def to_s; "cheese"; end end + BigDecimal.prepend BigDecimalOverride + value = BigDecimal('44.44') + print "#{value.to_s} #{value.inspect}" + EOF + end + + describe "when interacting with Rational" do + before :each do + @a = BigDecimal('166.666666666') + @b = Rational(500, 3) + @c = @a - @b end - it "allows for [eEdD] as exponent separator" do - reference = BigDecimal("12345.67E89") + # Check the input is as we understand it - BigDecimal("12345.67e89").should == reference - BigDecimal("12345.67E89").should == reference - BigDecimal("12345.67d89").should == reference - BigDecimal("12345.67D89").should == reference + it "has the LHS print as expected" do + @a.to_s.should == "0.166666666666e3" + @a.to_f.to_s.should == "166.666666666" + Float(@a).to_s.should == "166.666666666" end - it "allows for varying signs" do - reference = BigDecimal("123.456E1") - - BigDecimal("+123.456E1").should == reference - BigDecimal("-123.456E1").should == -reference - BigDecimal("123.456E+1").should == reference - BigDecimal("12345.6E-1").should == reference - BigDecimal("+123.456E+1").should == reference - BigDecimal("+12345.6E-1").should == reference - BigDecimal("-123.456E+1").should == -reference - BigDecimal("-12345.6E-1").should == -reference + it "has the RHS print as expected" do + @b.to_s.should == "500/3" + @b.to_f.to_s.should == "166.66666666666666" + Float(@b).to_s.should == "166.66666666666666" end - it "raises ArgumentError when Float is used without precision" do - -> { BigDecimal(1.0) }.should raise_error(ArgumentError) + it "has the expected precision on the LHS" do + suppress_warning { @a.precs[0] }.should == 18 end - it "returns appropriate BigDecimal zero for signed zero" do - BigDecimal(-0.0, Float::DIG).sign.should == -1 - BigDecimal(0.0, Float::DIG).sign.should == 1 + it "has the expected maximum precision on the LHS" do + suppress_warning { @a.precs[1] }.should == 27 end - it "pre-coerces long integers" do - BigDecimal(3).add(1 << 50, 3).should == BigDecimal('0.113e16') + it "produces the expected result when done via Float" do + (Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10" end - it "does not call to_s when calling inspect" do - value = BigDecimal('44.44') - value.to_s.should == '0.4444e2' - value.inspect.should == '0.4444e2' - - ruby_exe( <<-'EOF').should == "cheese 0.4444e2" - require 'bigdecimal' - module BigDecimalOverride - def to_s; "cheese"; end - end - BigDecimal.prepend BigDecimalOverride - value = BigDecimal('44.44') - print "#{value.to_s} #{value.inspect}" - EOF + it "produces the expected result when done via to_f" do + (@a.to_f - @b.to_f).to_s.should == "-6.666596163995564e-10" end - describe "when interacting with Rational" do - before :each do - @a = BigDecimal('166.666666666') - @b = Rational(500, 3) - @c = @a - @b - end - - # Check the input is as we understand it - - it "has the LHS print as expected" do - @a.to_s.should == "0.166666666666e3" - @a.to_f.to_s.should == "166.666666666" - Float(@a).to_s.should == "166.666666666" - end - - it "has the RHS print as expected" do - @b.to_s.should == "500/3" - @b.to_f.to_s.should == "166.66666666666666" - Float(@b).to_s.should == "166.66666666666666" - end - - it "has the expected precision on the LHS" do - suppress_warning { @a.precs[0] }.should == 18 - end - - it "has the expected maximum precision on the LHS" do - suppress_warning { @a.precs[1] }.should == 27 - end - - it "produces the expected result when done via Float" do - (Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10" - end + # Check underlying methods work as we understand - it "produces the expected result when done via to_f" do - (@a.to_f - @b.to_f).to_s.should == "-6.666596163995564e-10" - end - - # Check underlying methods work as we understand - - it "BigDecimal precision is the number of digits rounded up to a multiple of nine" do - 1.upto(100) do |n| - b = BigDecimal('4' * n) - precs, _ = suppress_warning { b.precs } - (precs >= 9).should be_true - (precs >= n).should be_true - (precs % 9).should == 0 - end - suppress_warning { BigDecimal('NaN').precs[0] }.should == 9 + it "BigDecimal precision is the number of digits rounded up to a multiple of nine" do + 1.upto(100) do |n| + b = BigDecimal('4' * n) + precs, _ = suppress_warning { b.precs } + (precs >= 9).should be_true + (precs >= n).should be_true + (precs % 9).should == 0 end + suppress_warning { BigDecimal('NaN').precs[0] }.should == 9 + end - it "BigDecimal maximum precision is nine more than precision except for abnormals" do - 1.upto(100) do |n| - b = BigDecimal('4' * n) - precs, max = suppress_warning { b.precs } - max.should == precs + 9 - end - suppress_warning { BigDecimal('NaN').precs[1] }.should == 9 + it "BigDecimal maximum precision is nine more than precision except for abnormals" do + 1.upto(100) do |n| + b = BigDecimal('4' * n) + precs, max = suppress_warning { b.precs } + max.should == precs + 9 end + suppress_warning { BigDecimal('NaN').precs[1] }.should == 9 + end - it "BigDecimal(Rational, 18) produces the result we expect" do - BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3" - end + it "BigDecimal(Rational, 18) produces the result we expect" do + BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3" + end - it "BigDecimal(Rational, BigDecimal.precs[0]) produces the result we expect" do - BigDecimal(@b, suppress_warning { @a.precs[0] }).to_s.should == "0.166666666666666667e3" - end + it "BigDecimal(Rational, BigDecimal.precs[0]) produces the result we expect" do + BigDecimal(@b, suppress_warning { @a.precs[0] }).to_s.should == "0.166666666666666667e3" + end - # Check the top-level expression works as we expect + # Check the top-level expression works as we expect - it "produces a BigDecimal" do - @c.class.should == BigDecimal - end + it "produces a BigDecimal" do + @c.class.should == BigDecimal + end - it "produces the expected result" do - @c.should == BigDecimal("-0.666667e-9") - @c.to_s.should == "-0.666667e-9" - end + it "produces the expected result" do + @c.should == BigDecimal("-0.666667e-9") + @c.to_s.should == "-0.666667e-9" + end - it "produces the correct class for other arithmetic operators" do - (@a + @b).class.should == BigDecimal - (@a * @b).class.should == BigDecimal - (@a / @b).class.should == BigDecimal - (@a % @b).class.should == BigDecimal - end + it "produces the correct class for other arithmetic operators" do + (@a + @b).class.should == BigDecimal + (@a * @b).class.should == BigDecimal + (@a / @b).class.should == BigDecimal + (@a % @b).class.should == BigDecimal end end end diff --git a/spec/ruby/library/bigdecimal/abs_spec.rb b/spec/ruby/library/bigdecimal/abs_spec.rb index 44ac0db77b956f..95dc45a90589e0 100644 --- a/spec/ruby/library/bigdecimal/abs_spec.rb +++ b/spec/ruby/library/bigdecimal/abs_spec.rb @@ -1,53 +1,50 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#abs" do - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @mixed = BigDecimal("1.23456789") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end - - it "returns the absolute value" do - pos_int = BigDecimal("2E5555") - neg_int = BigDecimal("-2E5555") - pos_frac = BigDecimal("2E-9999") - neg_frac = BigDecimal("-2E-9999") +describe "BigDecimal#abs" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @mixed = BigDecimal("1.23456789") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end - pos_int.abs.should == pos_int - neg_int.abs.should == pos_int - pos_frac.abs.should == pos_frac - neg_frac.abs.should == pos_frac - @one.abs.should == 1 - @two.abs.should == 2 - @three.abs.should == 3 - @mixed.abs.should == @mixed - @one_minus.abs.should == @one - end + it "returns the absolute value" do + pos_int = BigDecimal("2E5555") + neg_int = BigDecimal("-2E5555") + pos_frac = BigDecimal("2E-9999") + neg_frac = BigDecimal("-2E-9999") - it "properly handles special values" do - @infinity.abs.should == @infinity - @infinity_minus.abs.should == @infinity - @nan.abs.should.nan? # have to do it this way, since == doesn't work on NaN - @zero.abs.should == 0 - @zero.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO - @zero_pos.abs.should == 0 - @zero_pos.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO - @zero_neg.abs.should == 0 - @zero_neg.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO - end + pos_int.abs.should == pos_int + neg_int.abs.should == pos_int + pos_frac.abs.should == pos_frac + neg_frac.abs.should == pos_frac + @one.abs.should == 1 + @two.abs.should == 2 + @three.abs.should == 3 + @mixed.abs.should == @mixed + @one_minus.abs.should == @one + end + it "properly handles special values" do + @infinity.abs.should == @infinity + @infinity_minus.abs.should == @infinity + @nan.abs.should.nan? # have to do it this way, since == doesn't work on NaN + @zero.abs.should == 0 + @zero.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_pos.abs.should == 0 + @zero_pos.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO + @zero_neg.abs.should == 0 + @zero_neg.abs.sign.should == BigDecimal::SIGN_POSITIVE_ZERO end + end diff --git a/spec/ruby/library/bigdecimal/add_spec.rb b/spec/ruby/library/bigdecimal/add_spec.rb index 9fea1a021246a8..542713011d6a93 100644 --- a/spec/ruby/library/bigdecimal/add_spec.rb +++ b/spec/ruby/library/bigdecimal/add_spec.rb @@ -1,196 +1,193 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +require 'bigdecimal' + +describe "BigDecimal#add" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @ten = BigDecimal("10") + @eleven = BigDecimal("11") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @frac_3 = BigDecimal("12345E10") + @frac_4 = BigDecimal("98765E10") + @dot_ones = BigDecimal("0.1111111111") + end -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - - require 'bigdecimal' - - describe "BigDecimal#add" do - - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @ten = BigDecimal("10") - @eleven = BigDecimal("11") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - @frac_3 = BigDecimal("12345E10") - @frac_4 = BigDecimal("98765E10") - @dot_ones = BigDecimal("0.1111111111") - end + it "returns a + b with given precision" do + # documentation states that precision is optional, but it ain't, + @two.add(@one, 1).should == @three + @one .add(@two, 1).should == @three + @one.add(@one_minus, 1).should == @zero + @ten.add(@one, 2).should == @eleven + @zero.add(@one, 1).should == @one + @frac_2.add(@frac_1, 10000).should == BigDecimal("1.9E-99999") + @frac_1.add(@frac_1, 10000).should == BigDecimal("2E-99999") + @frac_3.add(@frac_4, 0).should == BigDecimal("0.11111E16") + @frac_3.add(@frac_4, 1).should == BigDecimal("0.1E16") + @frac_3.add(@frac_4, 2).should == BigDecimal("0.11E16") + @frac_3.add(@frac_4, 3).should == BigDecimal("0.111E16") + @frac_3.add(@frac_4, 4).should == BigDecimal("0.1111E16") + @frac_3.add(@frac_4, 5).should == BigDecimal("0.11111E16") + @frac_3.add(@frac_4, 6).should == BigDecimal("0.11111E16") + end - it "returns a + b with given precision" do - # documentation states that precision is optional, but it ain't, - @two.add(@one, 1).should == @three - @one .add(@two, 1).should == @three - @one.add(@one_minus, 1).should == @zero - @ten.add(@one, 2).should == @eleven - @zero.add(@one, 1).should == @one - @frac_2.add(@frac_1, 10000).should == BigDecimal("1.9E-99999") - @frac_1.add(@frac_1, 10000).should == BigDecimal("2E-99999") - @frac_3.add(@frac_4, 0).should == BigDecimal("0.11111E16") - @frac_3.add(@frac_4, 1).should == BigDecimal("0.1E16") - @frac_3.add(@frac_4, 2).should == BigDecimal("0.11E16") - @frac_3.add(@frac_4, 3).should == BigDecimal("0.111E16") - @frac_3.add(@frac_4, 4).should == BigDecimal("0.1111E16") - @frac_3.add(@frac_4, 5).should == BigDecimal("0.11111E16") - @frac_3.add(@frac_4, 6).should == BigDecimal("0.11111E16") - end + it "returns a + [Fixnum value] with given precision" do + (1..10).each {|precision| + @dot_ones.add(0, precision).should == BigDecimal("0." + "1" * precision) + } + BigDecimal("0.88").add(0, 1).should == BigDecimal("0.9") + end - it "returns a + [Fixnum value] with given precision" do - (1..10).each {|precision| - @dot_ones.add(0, precision).should == BigDecimal("0." + "1" * precision) - } - BigDecimal("0.88").add(0, 1).should == BigDecimal("0.9") - end + it "returns a + [Bignum value] with given precision" do + bignum = 10000000000000000000 + (1..20).each {|precision| + @dot_ones.add(bignum, precision).should == BigDecimal("0.1E20") + } + (21..30).each {|precision| + @dot_ones.add(bignum, precision).should == BigDecimal( + "0.10000000000000000000" + "1" * (precision - 20) + "E20") + } + end - it "returns a + [Bignum value] with given precision" do - bignum = 10000000000000000000 - (1..20).each {|precision| - @dot_ones.add(bignum, precision).should == BigDecimal("0.1E20") - } - (21..30).each {|precision| - @dot_ones.add(bignum, precision).should == BigDecimal( - "0.10000000000000000000" + "1" * (precision - 20) + "E20") - } +# TODO: +# https://blade.ruby-lang.org/ruby-core/17374 +# +# This doesn't work on MRI and looks like a bug to me: +# one can use BigDecimal + Float, but not Bigdecimal.add(Float) +# +# it "returns a + [Float value] with given precision" do +# (1..10).each {|precision| +# @dot_ones.add(0.0, precision).should == BigDecimal("0." + "1" * precision) +# } +# +# BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9") +# end + + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) + @frac_3.add(object, 1).should == BigDecimal("0.1E16") end + end - # TODO: - # https://blade.ruby-lang.org/ruby-core/17374 - # - # This doesn't work on MRI and looks like a bug to me: - # one can use BigDecimal + Float, but not Bigdecimal.add(Float) - # - # it "returns a + [Float value] with given precision" do - # (1..10).each {|precision| - # @dot_ones.add(0.0, precision).should == BigDecimal("0." + "1" * precision) - # } - # - # BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9") - # end - - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.add(object, 1).should == BigDecimal("0.1E16") - end + describe "with Rational" do + it "produces a BigDecimal" do + (@three + Rational(500, 2)).should == BigDecimal("0.253e3") end + end - describe "with Rational" do - it "produces a BigDecimal" do - (@three + Rational(500, 2)).should == BigDecimal("0.253e3") - end + it "favors the precision specified in the second argument over the global limit" do + BigDecimalSpecs.with_limit(1) do + BigDecimal('0.888').add(@zero, 3).should == BigDecimal('0.888') end - it "favors the precision specified in the second argument over the global limit" do - BigDecimalSpecs.with_limit(1) do - BigDecimal('0.888').add(@zero, 3).should == BigDecimal('0.888') - end - - BigDecimalSpecs.with_limit(2) do - BigDecimal('0.888').add(@zero, 1).should == BigDecimal('0.9') - end + BigDecimalSpecs.with_limit(2) do + BigDecimal('0.888').add(@zero, 1).should == BigDecimal('0.9') end + end - it "uses the current rounding mode if rounding is needed" do - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_UP) do - BigDecimal('0.111').add(@zero, 1).should == BigDecimal('0.2') - BigDecimal('-0.111').add(@zero, 1).should == BigDecimal('-0.2') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_DOWN) do - BigDecimal('0.999').add(@zero, 1).should == BigDecimal('0.9') - BigDecimal('-0.999').add(@zero, 1).should == BigDecimal('-0.9') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_UP) do - BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') - BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_DOWN) do - BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') - BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_EVEN) do - BigDecimal('0.75').add(@zero, 1).should == BigDecimal('0.8') - BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') - BigDecimal('-0.75').add(@zero, 1).should == BigDecimal('-0.8') - BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_CEILING) do - BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') - BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') - end - BigDecimalSpecs.with_rounding(BigDecimal::ROUND_FLOOR) do - BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') - BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') - end + it "uses the current rounding mode if rounding is needed" do + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_UP) do + BigDecimal('0.111').add(@zero, 1).should == BigDecimal('0.2') + BigDecimal('-0.111').add(@zero, 1).should == BigDecimal('-0.2') end - - it "uses the default ROUND_HALF_UP rounding if it wasn't explicitly changed" do + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_DOWN) do + BigDecimal('0.999').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.999').add(@zero, 1).should == BigDecimal('-0.9') + end + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_UP) do BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') end - - it "returns NaN if NaN is involved" do - @one.add(@nan, 10000).should.nan? - @nan.add(@one, 1).should.nan? + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_DOWN) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') end - - it "returns Infinity or -Infinity if these are involved" do - @zero.add(@infinity, 1).should == @infinity - @frac_2.add(@infinity, 1).should == @infinity - @one_minus.add(@infinity, 1).should == @infinity - @two.add(@infinity, 1).should == @infinity - - @zero.add(@infinity_minus, 1).should == @infinity_minus - @frac_2.add(@infinity_minus, 1).should == @infinity_minus - @one_minus.add(@infinity_minus, 1).should == @infinity_minus - @two.add(@infinity_minus, 1).should == @infinity_minus - - @infinity.add(@zero, 1).should == @infinity - @infinity.add(@frac_2, 1).should == @infinity - @infinity.add(@one_minus, 1).should == @infinity - @infinity.add(@two, 1).should == @infinity - - @infinity_minus.add(@zero, 1).should == @infinity_minus - @infinity_minus.add(@frac_2, 1).should == @infinity_minus - @infinity_minus.add(@one_minus, 1).should == @infinity_minus - @infinity_minus.add(@two, 1).should == @infinity_minus - - @infinity.add(@infinity, 10000).should == @infinity - @infinity_minus.add(@infinity_minus, 10000).should == @infinity_minus + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_HALF_EVEN) do + BigDecimal('0.75').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.75').add(@zero, 1).should == BigDecimal('-0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') end - - it "returns NaN if Infinity + (- Infinity)" do - @infinity.add(@infinity_minus, 10000).should.nan? - @infinity_minus.add(@infinity, 10000).should.nan? + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_CEILING) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.8') end - - it "raises TypeError when adds nil" do - -> { - @one.add(nil, 10) - }.should raise_error(TypeError) - -> { - @one.add(nil, 0) - }.should raise_error(TypeError) + BigDecimalSpecs.with_rounding(BigDecimal::ROUND_FLOOR) do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.8') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') end + end - it "raises TypeError when precision parameter is nil" do - -> { - @one.add(@one, nil) - }.should raise_error(TypeError) - end + it "uses the default ROUND_HALF_UP rounding if it wasn't explicitly changed" do + BigDecimal('0.85').add(@zero, 1).should == BigDecimal('0.9') + BigDecimal('-0.85').add(@zero, 1).should == BigDecimal('-0.9') + end - it "raises ArgumentError when precision parameter is negative" do - -> { - @one.add(@one, -10) - }.should raise_error(ArgumentError) - end + it "returns NaN if NaN is involved" do + @one.add(@nan, 10000).should.nan? + @nan.add(@one, 1).should.nan? + end + + it "returns Infinity or -Infinity if these are involved" do + @zero.add(@infinity, 1).should == @infinity + @frac_2.add(@infinity, 1).should == @infinity + @one_minus.add(@infinity, 1).should == @infinity + @two.add(@infinity, 1).should == @infinity + + @zero.add(@infinity_minus, 1).should == @infinity_minus + @frac_2.add(@infinity_minus, 1).should == @infinity_minus + @one_minus.add(@infinity_minus, 1).should == @infinity_minus + @two.add(@infinity_minus, 1).should == @infinity_minus + + @infinity.add(@zero, 1).should == @infinity + @infinity.add(@frac_2, 1).should == @infinity + @infinity.add(@one_minus, 1).should == @infinity + @infinity.add(@two, 1).should == @infinity + + @infinity_minus.add(@zero, 1).should == @infinity_minus + @infinity_minus.add(@frac_2, 1).should == @infinity_minus + @infinity_minus.add(@one_minus, 1).should == @infinity_minus + @infinity_minus.add(@two, 1).should == @infinity_minus + + @infinity.add(@infinity, 10000).should == @infinity + @infinity_minus.add(@infinity_minus, 10000).should == @infinity_minus + end + + it "returns NaN if Infinity + (- Infinity)" do + @infinity.add(@infinity_minus, 10000).should.nan? + @infinity_minus.add(@infinity, 10000).should.nan? + end + + it "raises TypeError when adds nil" do + -> { + @one.add(nil, 10) + }.should raise_error(TypeError) + -> { + @one.add(nil, 0) + }.should raise_error(TypeError) + end + + it "raises TypeError when precision parameter is nil" do + -> { + @one.add(@one, nil) + }.should raise_error(TypeError) + end + + it "raises ArgumentError when precision parameter is negative" do + -> { + @one.add(@one, -10) + }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/bigdecimal/case_compare_spec.rb b/spec/ruby/library/bigdecimal/case_compare_spec.rb index 7803346c791185..fac67143561980 100644 --- a/spec/ruby/library/bigdecimal/case_compare_spec.rb +++ b/spec/ruby/library/bigdecimal/case_compare_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require_relative 'shared/eql' -ruby_version_is ""..."3.4" do - require_relative 'shared/eql' - - describe "BigDecimal#===" do - it_behaves_like :bigdecimal_eql, :=== - end +describe "BigDecimal#===" do + it_behaves_like :bigdecimal_eql, :=== end diff --git a/spec/ruby/library/bigdecimal/ceil_spec.rb b/spec/ruby/library/bigdecimal/ceil_spec.rb index a3b6806be50ad6..60e71b12fb1528 100644 --- a/spec/ruby/library/bigdecimal/ceil_spec.rb +++ b/spec/ruby/library/bigdecimal/ceil_spec.rb @@ -1,107 +1,104 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#ceil" do + before :each do + @zero = BigDecimal("0") + @one = BigDecimal("1") + @three = BigDecimal("3") + @four = BigDecimal("4") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#ceil" do - before :each do - @zero = BigDecimal("0") - @one = BigDecimal("1") - @three = BigDecimal("3") - @four = BigDecimal("4") - @mixed = BigDecimal("1.23456789") - @mixed_big = BigDecimal("1.23456789E100") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - end + it "returns an Integer, if n is unspecified" do + @mixed.ceil.kind_of?(Integer).should == true + end - it "returns an Integer, if n is unspecified" do - @mixed.ceil.kind_of?(Integer).should == true - end + it "returns a BigDecimal, if n is specified" do + @pos_int.ceil(2).kind_of?(BigDecimal).should == true + end - it "returns a BigDecimal, if n is specified" do - @pos_int.ceil(2).kind_of?(BigDecimal).should == true - end + it "returns the smallest integer greater or equal to self, if n is unspecified" do + @pos_int.ceil.should == @pos_int + @neg_int.ceil.should == @neg_int + @pos_frac.ceil.should == BigDecimal("1") + @neg_frac.ceil.should == @zero + @zero.ceil.should == 0 + @zero_pos.ceil.should == @zero_pos + @zero_neg.ceil.should == @zero_neg + + + BigDecimal('2.3').ceil.should == 3 + BigDecimal('2.5').ceil.should == 3 + BigDecimal('2.9999').ceil.should == 3 + BigDecimal('-2.3').ceil.should == -2 + BigDecimal('-2.5').ceil.should == -2 + BigDecimal('-2.9999').ceil.should == -2 + end - it "returns the smallest integer greater or equal to self, if n is unspecified" do - @pos_int.ceil.should == @pos_int - @neg_int.ceil.should == @neg_int - @pos_frac.ceil.should == BigDecimal("1") - @neg_frac.ceil.should == @zero - @zero.ceil.should == 0 - @zero_pos.ceil.should == @zero_pos - @zero_neg.ceil.should == @zero_neg - - - BigDecimal('2.3').ceil.should == 3 - BigDecimal('2.5').ceil.should == 3 - BigDecimal('2.9999').ceil.should == 3 - BigDecimal('-2.3').ceil.should == -2 - BigDecimal('-2.5').ceil.should == -2 - BigDecimal('-2.9999').ceil.should == -2 - end + it "raise exception, if self is special value" do + -> { @infinity.ceil }.should raise_error(FloatDomainError) + -> { @infinity_neg.ceil }.should raise_error(FloatDomainError) + -> { @nan.ceil }.should raise_error(FloatDomainError) + end - it "raise exception, if self is special value" do - -> { @infinity.ceil }.should raise_error(FloatDomainError) - -> { @infinity_neg.ceil }.should raise_error(FloatDomainError) - -> { @nan.ceil }.should raise_error(FloatDomainError) - end + it "returns n digits right of the decimal point if given n > 0" do + @mixed.ceil(1).should == BigDecimal("1.3") + @mixed.ceil(5).should == BigDecimal("1.23457") - it "returns n digits right of the decimal point if given n > 0" do - @mixed.ceil(1).should == BigDecimal("1.3") - @mixed.ceil(5).should == BigDecimal("1.23457") - - BigDecimal("-0.03").ceil(1).should == BigDecimal("0") - BigDecimal("0.03").ceil(1).should == BigDecimal("0.1") - - BigDecimal("23.45").ceil(0).should == BigDecimal('24') - BigDecimal("23.45").ceil(1).should == BigDecimal('23.5') - BigDecimal("23.45").ceil(2).should == BigDecimal('23.45') - - BigDecimal("-23.45").ceil(0).should == BigDecimal('-23') - BigDecimal("-23.45").ceil(1).should == BigDecimal('-23.4') - BigDecimal("-23.45").ceil(2).should == BigDecimal('-23.45') - - BigDecimal("2E-10").ceil(0).should == @one - BigDecimal("2E-10").ceil(9).should == BigDecimal('1E-9') - BigDecimal("2E-10").ceil(10).should == BigDecimal('2E-10') - BigDecimal("2E-10").ceil(11).should == BigDecimal('2E-10') - - (1..10).each do |n| - # 0.4, 0.34, 0.334, etc. - (@one.div(@three,20)).ceil(n).should == BigDecimal("0.#{'3'*(n-1)}4") - # 1.4, 1.34, 1.334, etc. - (@four.div(@three,20)).ceil(n).should == BigDecimal("1.#{'3'*(n-1)}4") - (BigDecimal('31').div(@three,20)).ceil(n).should == BigDecimal("10.#{'3'*(n-1)}4") - end - (1..10).each do |n| - # -0.4, -0.34, -0.334, etc. - (-@one.div(@three,20)).ceil(n).should == BigDecimal("-0.#{'3'* n}") - end - (1..10).each do |n| - (@three.div(@one,20)).ceil(n).should == @three - end - (1..10).each do |n| - (-@three.div(@one,20)).ceil(n).should == -@three - end - end + BigDecimal("-0.03").ceil(1).should == BigDecimal("0") + BigDecimal("0.03").ceil(1).should == BigDecimal("0.1") + + BigDecimal("23.45").ceil(0).should == BigDecimal('24') + BigDecimal("23.45").ceil(1).should == BigDecimal('23.5') + BigDecimal("23.45").ceil(2).should == BigDecimal('23.45') + + BigDecimal("-23.45").ceil(0).should == BigDecimal('-23') + BigDecimal("-23.45").ceil(1).should == BigDecimal('-23.4') + BigDecimal("-23.45").ceil(2).should == BigDecimal('-23.45') - it "sets n digits left of the decimal point to 0, if given n < 0" do - BigDecimal("13345.234").ceil(-2).should == BigDecimal("13400.0") - @mixed_big.ceil(-99).should == BigDecimal("0.13E101") - @mixed_big.ceil(-100).should == BigDecimal("0.2E101") - @mixed_big.ceil(-95).should == BigDecimal("0.123457E101") - BigDecimal("1E10").ceil(-30).should == BigDecimal('1E30') - BigDecimal("-1E10").ceil(-30).should == @zero + BigDecimal("2E-10").ceil(0).should == @one + BigDecimal("2E-10").ceil(9).should == BigDecimal('1E-9') + BigDecimal("2E-10").ceil(10).should == BigDecimal('2E-10') + BigDecimal("2E-10").ceil(11).should == BigDecimal('2E-10') + + (1..10).each do |n| + # 0.4, 0.34, 0.334, etc. + (@one.div(@three,20)).ceil(n).should == BigDecimal("0.#{'3'*(n-1)}4") + # 1.4, 1.34, 1.334, etc. + (@four.div(@three,20)).ceil(n).should == BigDecimal("1.#{'3'*(n-1)}4") + (BigDecimal('31').div(@three,20)).ceil(n).should == BigDecimal("10.#{'3'*(n-1)}4") + end + (1..10).each do |n| + # -0.4, -0.34, -0.334, etc. + (-@one.div(@three,20)).ceil(n).should == BigDecimal("-0.#{'3'* n}") end + (1..10).each do |n| + (@three.div(@one,20)).ceil(n).should == @three + end + (1..10).each do |n| + (-@three.div(@one,20)).ceil(n).should == -@three + end + end + it "sets n digits left of the decimal point to 0, if given n < 0" do + BigDecimal("13345.234").ceil(-2).should == BigDecimal("13400.0") + @mixed_big.ceil(-99).should == BigDecimal("0.13E101") + @mixed_big.ceil(-100).should == BigDecimal("0.2E101") + @mixed_big.ceil(-95).should == BigDecimal("0.123457E101") + BigDecimal("1E10").ceil(-30).should == BigDecimal('1E30') + BigDecimal("-1E10").ceil(-30).should == @zero end + end diff --git a/spec/ruby/library/bigdecimal/clone_spec.rb b/spec/ruby/library/bigdecimal/clone_spec.rb index 8bb073b7b2c42a..b3a1c61d6a6a01 100644 --- a/spec/ruby/library/bigdecimal/clone_spec.rb +++ b/spec/ruby/library/bigdecimal/clone_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require_relative 'shared/clone' -ruby_version_is ""..."3.4" do - require_relative 'shared/clone' - - describe "BigDecimal#dup" do - it_behaves_like :bigdecimal_clone, :clone - end +describe "BigDecimal#dup" do + it_behaves_like :bigdecimal_clone, :clone end diff --git a/spec/ruby/library/bigdecimal/coerce_spec.rb b/spec/ruby/library/bigdecimal/coerce_spec.rb index a12997ffcd9e40..1e5c73f9690b20 100644 --- a/spec/ruby/library/bigdecimal/coerce_spec.rb +++ b/spec/ruby/library/bigdecimal/coerce_spec.rb @@ -1,29 +1,26 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#coerce" do - describe "BigDecimal#coerce" do - - it "returns [other, self] both as BigDecimal" do - one = BigDecimal("1.0") - five_point_28 = BigDecimal("5.28") - zero_minus = BigDecimal("-0.0") - some_value = 32434234234234234234 - - BigDecimal("1.2").coerce(1).should == [one, BigDecimal("1.2")] - five_point_28.coerce(1.0).should == [one, BigDecimal("5.28")] - one.coerce(one).should == [one, one] - one.coerce(2.5).should == [2.5, one] - BigDecimal("1").coerce(3.14).should == [3.14, one] - a, b = zero_minus.coerce(some_value) - a.should == BigDecimal(some_value.to_s) - b.should == zero_minus - a, b = one.coerce(some_value) - a.should == BigDecimal(some_value.to_s) - b.to_f.should be_close(1.0, TOLERANCE) # can we take out the to_f once BigDecimal#- is implemented? - b.should == one - end + it "returns [other, self] both as BigDecimal" do + one = BigDecimal("1.0") + five_point_28 = BigDecimal("5.28") + zero_minus = BigDecimal("-0.0") + some_value = 32434234234234234234 + BigDecimal("1.2").coerce(1).should == [one, BigDecimal("1.2")] + five_point_28.coerce(1.0).should == [one, BigDecimal("5.28")] + one.coerce(one).should == [one, one] + one.coerce(2.5).should == [2.5, one] + BigDecimal("1").coerce(3.14).should == [3.14, one] + a, b = zero_minus.coerce(some_value) + a.should == BigDecimal(some_value.to_s) + b.should == zero_minus + a, b = one.coerce(some_value) + a.should == BigDecimal(some_value.to_s) + b.to_f.should be_close(1.0, TOLERANCE) # can we take out the to_f once BigDecimal#- is implemented? + b.should == one end + end diff --git a/spec/ruby/library/bigdecimal/comparison_spec.rb b/spec/ruby/library/bigdecimal/comparison_spec.rb index ae6f00dd9a7c56..c53187b727d830 100644 --- a/spec/ruby/library/bigdecimal/comparison_spec.rb +++ b/spec/ruby/library/bigdecimal/comparison_spec.rb @@ -1,84 +1,81 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#<=>" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") - describe "BigDecimal#<=>" do - before :each do - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @mixed = BigDecimal("1.23456789") - @mixed_big = BigDecimal("1.23456789E100") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @int_mock = mock('123') - class << @int_mock - def coerce(other) - return [other, BigDecimal('123')] - end - def >=(other) - BigDecimal('123') >= other - end + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >=(other) + BigDecimal('123') >= other end + end - @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, - -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, - @zero , 1, 2, 10, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - end + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + end - it "returns 0 if a == b" do - (@pos_int <=> @pos_int).should == 0 - (@neg_int <=> @neg_int).should == 0 - (@pos_frac <=> @pos_frac).should == 0 - (@neg_frac <=> @neg_frac).should == 0 - (@zero <=> @zero).should == 0 - (@infinity <=> @infinity).should == 0 - (@infinity_neg <=> @infinity_neg).should == 0 - end + it "returns 0 if a == b" do + (@pos_int <=> @pos_int).should == 0 + (@neg_int <=> @neg_int).should == 0 + (@pos_frac <=> @pos_frac).should == 0 + (@neg_frac <=> @neg_frac).should == 0 + (@zero <=> @zero).should == 0 + (@infinity <=> @infinity).should == 0 + (@infinity_neg <=> @infinity_neg).should == 0 + end - it "returns 1 if a > b" do - (@pos_int <=> @neg_int).should == 1 - (@pos_frac <=> @neg_frac).should == 1 - (@pos_frac <=> @zero).should == 1 - @values.each { |val| - (@infinity <=> val).should == 1 - } - end + it "returns 1 if a > b" do + (@pos_int <=> @neg_int).should == 1 + (@pos_frac <=> @neg_frac).should == 1 + (@pos_frac <=> @zero).should == 1 + @values.each { |val| + (@infinity <=> val).should == 1 + } + end - it "returns -1 if a < b" do - (@zero <=> @pos_frac).should == -1 - (@neg_int <=> @pos_frac).should == -1 - (@pos_frac <=> @pos_int).should == -1 - @values.each { |val| - (@infinity_neg <=> val).should == -1 - } - end + it "returns -1 if a < b" do + (@zero <=> @pos_frac).should == -1 + (@neg_int <=> @pos_frac).should == -1 + (@pos_frac <=> @pos_int).should == -1 + @values.each { |val| + (@infinity_neg <=> val).should == -1 + } + end - it "returns nil if NaN is involved" do - @values += [@infinity, @infinity_neg, @nan] - @values << nil - @values << Object.new - @values.each { |val| - (@nan <=> val).should == nil - } - end + it "returns nil if NaN is involved" do + @values += [@infinity, @infinity_neg, @nan] + @values << nil + @values << Object.new + @values.each { |val| + (@nan <=> val).should == nil + } + end - it "returns nil if the argument is nil" do - (@zero <=> nil).should == nil - (@infinity <=> nil).should == nil - (@infinity_neg <=> nil).should == nil - (@mixed <=> nil).should == nil - (@pos_int <=> nil).should == nil - (@neg_frac <=> nil).should == nil - end + it "returns nil if the argument is nil" do + (@zero <=> nil).should == nil + (@infinity <=> nil).should == nil + (@infinity_neg <=> nil).should == nil + (@mixed <=> nil).should == nil + (@pos_int <=> nil).should == nil + (@neg_frac <=> nil).should == nil end end diff --git a/spec/ruby/library/bigdecimal/constants_spec.rb b/spec/ruby/library/bigdecimal/constants_spec.rb index 6779a727c366ee..8d879c036acc00 100644 --- a/spec/ruby/library/bigdecimal/constants_spec.rb +++ b/spec/ruby/library/bigdecimal/constants_spec.rb @@ -1,72 +1,69 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal constants" do - it "defines a VERSION value" do - BigDecimal.const_defined?(:VERSION).should be_true - end +describe "BigDecimal constants" do + it "defines a VERSION value" do + BigDecimal.const_defined?(:VERSION).should be_true + end - it "has a BASE value" do - # The actual one is decided based on HAVE_INT64_T in MRI, - # which is hard to check here. - [10000, 1000000000].should include(BigDecimal::BASE) - end + it "has a BASE value" do + # The actual one is decided based on HAVE_INT64_T in MRI, + # which is hard to check here. + [10000, 1000000000].should include(BigDecimal::BASE) + end - it "has a NaN value" do - BigDecimal::NAN.nan?.should be_true - end + it "has a NaN value" do + BigDecimal::NAN.nan?.should be_true + end - it "has an INFINITY value" do - BigDecimal::INFINITY.infinite?.should == 1 - end + it "has an INFINITY value" do + BigDecimal::INFINITY.infinite?.should == 1 + end - describe "exception-related constants" do - [ - [:EXCEPTION_ALL, 0xff], - [:EXCEPTION_INFINITY, 0x01], - [:EXCEPTION_NaN, 0x02], - [:EXCEPTION_UNDERFLOW, 0x04], - [:EXCEPTION_OVERFLOW, 0x01], - [:EXCEPTION_ZERODIVIDE, 0x10] - ].each do |const, value| - it "has a #{const} value" do - BigDecimal.const_get(const).should == value - end + describe "exception-related constants" do + [ + [:EXCEPTION_ALL, 0xff], + [:EXCEPTION_INFINITY, 0x01], + [:EXCEPTION_NaN, 0x02], + [:EXCEPTION_UNDERFLOW, 0x04], + [:EXCEPTION_OVERFLOW, 0x01], + [:EXCEPTION_ZERODIVIDE, 0x10] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value end end + end - describe "rounding-related constants" do - [ - [:ROUND_MODE, 0x100], - [:ROUND_UP, 1], - [:ROUND_DOWN, 2], - [:ROUND_HALF_UP, 3], - [:ROUND_HALF_DOWN, 4], - [:ROUND_CEILING, 5], - [:ROUND_FLOOR, 6], - [:ROUND_HALF_EVEN, 7] - ].each do |const, value| - it "has a #{const} value" do - BigDecimal.const_get(const).should == value - end + describe "rounding-related constants" do + [ + [:ROUND_MODE, 0x100], + [:ROUND_UP, 1], + [:ROUND_DOWN, 2], + [:ROUND_HALF_UP, 3], + [:ROUND_HALF_DOWN, 4], + [:ROUND_CEILING, 5], + [:ROUND_FLOOR, 6], + [:ROUND_HALF_EVEN, 7] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value end end + end - describe "sign-related constants" do - [ - [:SIGN_NaN, 0], - [:SIGN_POSITIVE_ZERO, 1], - [:SIGN_NEGATIVE_ZERO, -1], - [:SIGN_POSITIVE_FINITE, 2], - [:SIGN_NEGATIVE_FINITE, -2], - [:SIGN_POSITIVE_INFINITE, 3], - [:SIGN_NEGATIVE_INFINITE, -3] - ].each do |const, value| - it "has a #{const} value" do - BigDecimal.const_get(const).should == value - end + describe "sign-related constants" do + [ + [:SIGN_NaN, 0], + [:SIGN_POSITIVE_ZERO, 1], + [:SIGN_NEGATIVE_ZERO, -1], + [:SIGN_POSITIVE_FINITE, 2], + [:SIGN_NEGATIVE_FINITE, -2], + [:SIGN_POSITIVE_INFINITE, 3], + [:SIGN_NEGATIVE_INFINITE, -3] + ].each do |const, value| + it "has a #{const} value" do + BigDecimal.const_get(const).should == value end end end diff --git a/spec/ruby/library/bigdecimal/div_spec.rb b/spec/ruby/library/bigdecimal/div_spec.rb index 7e0b8fada75537..53ad6d04184d87 100644 --- a/spec/ruby/library/bigdecimal/div_spec.rb +++ b/spec/ruby/library/bigdecimal/div_spec.rb @@ -1,113 +1,110 @@ require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' + +describe "BigDecimal#div with precision set to 0" do + # TODO: figure out if there is a better way to do these + # shared specs rather than sending [0]. See other specs + # that share :bigdecimal_quo. + it_behaves_like :bigdecimal_quo, :div, [0] +end -ruby_version_is ""..."3.4" do - require_relative 'shared/quo' - require 'bigdecimal' - - describe "BigDecimal#div with precision set to 0" do - # TODO: figure out if there is a better way to do these - # shared specs rather than sending [0]. See other specs - # that share :bigdecimal_quo. - it_behaves_like :bigdecimal_quo, :div, [0] +describe "BigDecimal#div" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_plus = BigDecimal("+0") + @zero_minus = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") end - describe "BigDecimal#div" do - - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_plus = BigDecimal("+0") - @zero_minus = BigDecimal("-0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end - - it "returns a / b with optional precision" do - @two.div(@one).should == @two - @one.div(@two).should == @zero - # ^^ is this really intended for a class with arbitrary precision? - @one.div(@two, 1).should == BigDecimal("0.5") - @one.div(@one_minus).should == @one_minus - @one_minus.div(@one_minus).should == @one - @frac_2.div(@frac_1, 1).should == BigDecimal("0.9") - @frac_1.div(@frac_1).should == @one - - res = "0." + "3" * 1000 - (1..100).each { |idx| - @one.div(@three, idx).to_s("F").should == "0." + res[2, idx] - } - end + it "returns a / b with optional precision" do + @two.div(@one).should == @two + @one.div(@two).should == @zero + # ^^ is this really intended for a class with arbitrary precision? + @one.div(@two, 1).should == BigDecimal("0.5") + @one.div(@one_minus).should == @one_minus + @one_minus.div(@one_minus).should == @one + @frac_2.div(@frac_1, 1).should == BigDecimal("0.9") + @frac_1.div(@frac_1).should == @one + + res = "0." + "3" * 1000 + (1..100).each { |idx| + @one.div(@three, idx).to_s("F").should == "0." + res[2, idx] + } + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@one).and_return([@one, @two]) - @one.div(object).should == @zero - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, @two]) + @one.div(object).should == @zero end + end - it "raises FloatDomainError if NaN is involved" do - -> { @one.div(@nan) }.should raise_error(FloatDomainError) - -> { @nan.div(@one) }.should raise_error(FloatDomainError) - -> { @nan.div(@nan) }.should raise_error(FloatDomainError) - end + it "raises FloatDomainError if NaN is involved" do + -> { @one.div(@nan) }.should raise_error(FloatDomainError) + -> { @nan.div(@one) }.should raise_error(FloatDomainError) + -> { @nan.div(@nan) }.should raise_error(FloatDomainError) + end - it "returns 0 if divided by Infinity and no precision given" do - @zero.div(@infinity).should == 0 - @frac_2.div(@infinity).should == 0 - end + it "returns 0 if divided by Infinity and no precision given" do + @zero.div(@infinity).should == 0 + @frac_2.div(@infinity).should == 0 + end - it "returns 0 if divided by Infinity with given precision" do - @zero.div(@infinity, 0).should == 0 - @frac_2.div(@infinity, 1).should == 0 - @zero.div(@infinity, 100000).should == 0 - @frac_2.div(@infinity, 100000).should == 0 - end + it "returns 0 if divided by Infinity with given precision" do + @zero.div(@infinity, 0).should == 0 + @frac_2.div(@infinity, 1).should == 0 + @zero.div(@infinity, 100000).should == 0 + @frac_2.div(@infinity, 100000).should == 0 + end - it "raises ZeroDivisionError if divided by zero and no precision given" do - -> { @one.div(@zero) }.should raise_error(ZeroDivisionError) - -> { @one.div(@zero_plus) }.should raise_error(ZeroDivisionError) - -> { @one.div(@zero_minus) }.should raise_error(ZeroDivisionError) + it "raises ZeroDivisionError if divided by zero and no precision given" do + -> { @one.div(@zero) }.should raise_error(ZeroDivisionError) + -> { @one.div(@zero_plus) }.should raise_error(ZeroDivisionError) + -> { @one.div(@zero_minus) }.should raise_error(ZeroDivisionError) - -> { @zero.div(@zero) }.should raise_error(ZeroDivisionError) - -> { @zero_minus.div(@zero_plus) }.should raise_error(ZeroDivisionError) - -> { @zero_minus.div(@zero_minus) }.should raise_error(ZeroDivisionError) - -> { @zero_plus.div(@zero_minus) }.should raise_error(ZeroDivisionError) - end + -> { @zero.div(@zero) }.should raise_error(ZeroDivisionError) + -> { @zero_minus.div(@zero_plus) }.should raise_error(ZeroDivisionError) + -> { @zero_minus.div(@zero_minus) }.should raise_error(ZeroDivisionError) + -> { @zero_plus.div(@zero_minus) }.should raise_error(ZeroDivisionError) + end - it "returns NaN if zero is divided by zero" do - @zero.div(@zero, 0).should.nan? - @zero_minus.div(@zero_plus, 0).should.nan? - @zero_plus.div(@zero_minus, 0).should.nan? + it "returns NaN if zero is divided by zero" do + @zero.div(@zero, 0).should.nan? + @zero_minus.div(@zero_plus, 0).should.nan? + @zero_plus.div(@zero_minus, 0).should.nan? - @zero.div(@zero, 10).should.nan? - @zero_minus.div(@zero_plus, 10).should.nan? - @zero_plus.div(@zero_minus, 10).should.nan? - end + @zero.div(@zero, 10).should.nan? + @zero_minus.div(@zero_plus, 10).should.nan? + @zero_plus.div(@zero_minus, 10).should.nan? + end - it "raises FloatDomainError if (+|-) Infinity divided by 1 and no precision given" do - -> { @infinity_minus.div(@one) }.should raise_error(FloatDomainError) - -> { @infinity.div(@one) }.should raise_error(FloatDomainError) - -> { @infinity_minus.div(@one_minus) }.should raise_error(FloatDomainError) - end + it "raises FloatDomainError if (+|-) Infinity divided by 1 and no precision given" do + -> { @infinity_minus.div(@one) }.should raise_error(FloatDomainError) + -> { @infinity.div(@one) }.should raise_error(FloatDomainError) + -> { @infinity_minus.div(@one_minus) }.should raise_error(FloatDomainError) + end - it "returns (+|-)Infinity if (+|-)Infinity by 1 and precision given" do - @infinity_minus.div(@one, 0).should == @infinity_minus - @infinity.div(@one, 0).should == @infinity - @infinity_minus.div(@one_minus, 0).should == @infinity - end + it "returns (+|-)Infinity if (+|-)Infinity by 1 and precision given" do + @infinity_minus.div(@one, 0).should == @infinity_minus + @infinity.div(@one, 0).should == @infinity + @infinity_minus.div(@one_minus, 0).should == @infinity + end - it "returns NaN if Infinity / ((+|-) Infinity)" do - @infinity.div(@infinity_minus, 100000).should.nan? - @infinity_minus.div(@infinity, 1).should.nan? - end + it "returns NaN if Infinity / ((+|-) Infinity)" do + @infinity.div(@infinity_minus, 100000).should.nan? + @infinity_minus.div(@infinity, 1).should.nan? + end - end end diff --git a/spec/ruby/library/bigdecimal/divide_spec.rb b/spec/ruby/library/bigdecimal/divide_spec.rb index 77ca878402ea96..c62b23557dec7a 100644 --- a/spec/ruby/library/bigdecimal/divide_spec.rb +++ b/spec/ruby/library/bigdecimal/divide_spec.rb @@ -1,20 +1,17 @@ require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/quo' - require 'bigdecimal' +describe "BigDecimal#/" do + it_behaves_like :bigdecimal_quo, :/, [] - describe "BigDecimal#/" do - it_behaves_like :bigdecimal_quo, :/, [] - - before :each do - @three = BigDecimal("3") - end + before :each do + @three = BigDecimal("3") + end - describe "with Rational" do - it "produces a BigDecimal" do - (@three / Rational(500, 2)).should == BigDecimal("0.12e-1") - end + describe "with Rational" do + it "produces a BigDecimal" do + (@three / Rational(500, 2)).should == BigDecimal("0.12e-1") end end end diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb index 8b42f2329e4271..294f01cba01484 100644 --- a/spec/ruby/library/bigdecimal/divmod_spec.rb +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -1,183 +1,180 @@ require_relative '../../spec_helper' - -ruby_version_is ""..."3.4" do - require_relative 'shared/modulo' - require 'bigdecimal' - - module DivmodSpecs - def self.check_both_nan(array) - array.length.should == 2 - array[0].should.nan? - array[1].should.nan? - end - def self.check_both_bigdecimal(array) - array.length.should == 2 - array[0].kind_of?(BigDecimal).should == true - array[1].kind_of?(BigDecimal).should == true - end +require_relative 'shared/modulo' +require 'bigdecimal' + +module DivmodSpecs + def self.check_both_nan(array) + array.length.should == 2 + array[0].should.nan? + array[1].should.nan? + end + def self.check_both_bigdecimal(array) + array.length.should == 2 + array[0].kind_of?(BigDecimal).should == true + array[1].kind_of?(BigDecimal).should == true end +end - # TODO: figure out a way to do the shared specs with helpers instead - # of spec'ing a method that does not really exist - describe "BigDecimal#mod_part_of_divmod" do - # BigDecimal#divmod[1] behaves exactly like #modulo - before :all do - class BigDecimal - def mod_part_of_divmod(arg) - divmod(arg)[1] - end +# TODO: figure out a way to do the shared specs with helpers instead +# of spec'ing a method that does not really exist +describe "BigDecimal#mod_part_of_divmod" do + # BigDecimal#divmod[1] behaves exactly like #modulo + before :all do + class BigDecimal + def mod_part_of_divmod(arg) + divmod(arg)[1] end end + end - after :all do - class BigDecimal - undef mod_part_of_divmod - end + after :all do + class BigDecimal + undef mod_part_of_divmod end + end - it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod + it_behaves_like :bigdecimal_modulo, :mod_part_of_divmod - it "raises ZeroDivisionError if other is zero" do - bd5667 = BigDecimal("5667.19") + it "raises ZeroDivisionError if other is zero" do + bd5667 = BigDecimal("5667.19") - -> { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError) - -> { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError) - -> { @zero.mod_part_of_divmod(@zero) }.should raise_error(ZeroDivisionError) - end + -> { bd5667.mod_part_of_divmod(0) }.should raise_error(ZeroDivisionError) + -> { bd5667.mod_part_of_divmod(BigDecimal("0")) }.should raise_error(ZeroDivisionError) + -> { @zero.mod_part_of_divmod(@zero) }.should raise_error(ZeroDivisionError) end +end - describe "BigDecimal#divmod" do - - before :each do - @a = BigDecimal("42.00000000000000000001") - - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - - @one = BigDecimal("1") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - - @special_vals = [@infinity, @infinity_minus, @nan] - @regular_vals = [ - @one, @mixed, @pos_int, @neg_int, @pos_frac, - @neg_frac, @one_minus, @frac_1, @frac_2] - @zeroes = [@zero, @zero_pos, @zero_neg] - end +describe "BigDecimal#divmod" do + + before :each do + @a = BigDecimal("42.00000000000000000001") + + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + + @one = BigDecimal("1") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + + @special_vals = [@infinity, @infinity_minus, @nan] + @regular_vals = [ + @one, @mixed, @pos_int, @neg_int, @pos_frac, + @neg_frac, @one_minus, @frac_1, @frac_2] + @zeroes = [@zero, @zero_pos, @zero_neg] + end - it "divides value, returns an array" do - res = @a.divmod(5) - res.kind_of?(Array).should == true - end + it "divides value, returns an array" do + res = @a.divmod(5) + res.kind_of?(Array).should == true + end - it "array contains quotient and modulus as BigDecimal" do - res = @a.divmod(5) - DivmodSpecs.check_both_bigdecimal(res) - res[0].should == BigDecimal('0.8E1') - res[1].should == BigDecimal('2.00000000000000000001') + it "array contains quotient and modulus as BigDecimal" do + res = @a.divmod(5) + DivmodSpecs.check_both_bigdecimal(res) + res[0].should == BigDecimal('0.8E1') + res[1].should == BigDecimal('2.00000000000000000001') - BigDecimal('1').divmod(BigDecimal('2')).should == [0, 1] - BigDecimal('2').divmod(BigDecimal('1')).should == [2, 0] + BigDecimal('1').divmod(BigDecimal('2')).should == [0, 1] + BigDecimal('2').divmod(BigDecimal('1')).should == [2, 0] - BigDecimal('1').divmod(BigDecimal('-2')).should == [-1, -1] - BigDecimal('2').divmod(BigDecimal('-1')).should == [-2, 0] + BigDecimal('1').divmod(BigDecimal('-2')).should == [-1, -1] + BigDecimal('2').divmod(BigDecimal('-1')).should == [-2, 0] - BigDecimal('-1').divmod(BigDecimal('2')).should == [-1, 1] - BigDecimal('-2').divmod(BigDecimal('1')).should == [-2, 0] - end + BigDecimal('-1').divmod(BigDecimal('2')).should == [-1, 1] + BigDecimal('-2').divmod(BigDecimal('1')).should == [-2, 0] + end - it "can be reversed with * and +" do - # Example taken from BigDecimal documentation - a = BigDecimal("42") - b = BigDecimal("9") - q, m = a.divmod(b) - c = q * b + m - a.should == c - - values = [@one, @one_minus, BigDecimal('2'), BigDecimal('-2'), - BigDecimal('5'), BigDecimal('-5'), BigDecimal('10'), BigDecimal('-10'), - BigDecimal('20'), BigDecimal('-20'), BigDecimal('100'), BigDecimal('-100'), - BigDecimal('1.23456789E10'), BigDecimal('-1.23456789E10') - ] - - # TODO: file MRI bug: - # BigDecimal('1').divmod(BigDecimal('3E-9'))[0] #=> 0.3E9, - # but really should be 0.333333333E9 - values << BigDecimal('1E-10') - values << BigDecimal('-1E-10') - values << BigDecimal('2E55') - values << BigDecimal('-2E55') - values << BigDecimal('2E-5555') - values << BigDecimal('-2E-5555') - - - values_and_zeroes = values + @zeroes - values_and_zeroes.each do |val1| - values.each do |val2| - res = val1.divmod(val2) - DivmodSpecs.check_both_bigdecimal(res) - res[0].should == ((val1/val2).floor) - res[1].should == (val1 - res[0] * val2) - end + it "can be reversed with * and +" do + # Example taken from BigDecimal documentation + a = BigDecimal("42") + b = BigDecimal("9") + q, m = a.divmod(b) + c = q * b + m + a.should == c + + values = [@one, @one_minus, BigDecimal('2'), BigDecimal('-2'), + BigDecimal('5'), BigDecimal('-5'), BigDecimal('10'), BigDecimal('-10'), + BigDecimal('20'), BigDecimal('-20'), BigDecimal('100'), BigDecimal('-100'), + BigDecimal('1.23456789E10'), BigDecimal('-1.23456789E10') + ] + + # TODO: file MRI bug: + # BigDecimal('1').divmod(BigDecimal('3E-9'))[0] #=> 0.3E9, + # but really should be 0.333333333E9 + values << BigDecimal('1E-10') + values << BigDecimal('-1E-10') + values << BigDecimal('2E55') + values << BigDecimal('-2E55') + values << BigDecimal('2E-5555') + values << BigDecimal('-2E-5555') + + + values_and_zeroes = values + @zeroes + values_and_zeroes.each do |val1| + values.each do |val2| + res = val1.divmod(val2) + DivmodSpecs.check_both_bigdecimal(res) + res[0].should == ((val1/val2).floor) + res[1].should == (val1 - res[0] * val2) end end + end - it "returns an array of two NaNs if NaN is involved" do - (@special_vals + @regular_vals + @zeroes).each do |val| - DivmodSpecs.check_both_nan(val.divmod(@nan)) - DivmodSpecs.check_both_nan(@nan.divmod(val)) - end + it "returns an array of two NaNs if NaN is involved" do + (@special_vals + @regular_vals + @zeroes).each do |val| + DivmodSpecs.check_both_nan(val.divmod(@nan)) + DivmodSpecs.check_both_nan(@nan.divmod(val)) end + end - it "raises ZeroDivisionError if the divisor is zero" do - (@special_vals + @regular_vals + @zeroes - [@nan]).each do |val| - @zeroes.each do |zero| - -> { val.divmod(zero) }.should raise_error(ZeroDivisionError) - end + it "raises ZeroDivisionError if the divisor is zero" do + (@special_vals + @regular_vals + @zeroes - [@nan]).each do |val| + @zeroes.each do |zero| + -> { val.divmod(zero) }.should raise_error(ZeroDivisionError) end end + end - it "returns an array of Infinity and NaN if the dividend is Infinity" do - @regular_vals.each do |val| - array = @infinity.divmod(val) - array.length.should == 2 - array[0].infinite?.should == (val > 0 ? 1 : -1) - array[1].should.nan? - end + it "returns an array of Infinity and NaN if the dividend is Infinity" do + @regular_vals.each do |val| + array = @infinity.divmod(val) + array.length.should == 2 + array[0].infinite?.should == (val > 0 ? 1 : -1) + array[1].should.nan? end + end - it "returns an array of zero and the dividend if the divisor is Infinity" do - @regular_vals.each do |val| - array = val.divmod(@infinity) - array.length.should == 2 - array[0].should == @zero - array[1].should == val - end + it "returns an array of zero and the dividend if the divisor is Infinity" do + @regular_vals.each do |val| + array = val.divmod(@infinity) + array.length.should == 2 + array[0].should == @zero + array[1].should == val end + end - it "returns an array of two zero if the dividend is zero" do - @zeroes.each do |zero| - @regular_vals.each do |val| - zero.divmod(val).should == [@zero, @zero] - end + it "returns an array of two zero if the dividend is zero" do + @zeroes.each do |zero| + @regular_vals.each do |val| + zero.divmod(val).should == [@zero, @zero] end end + end - it "raises TypeError if the argument cannot be coerced to BigDecimal" do - -> { - @one.divmod('1') - }.should raise_error(TypeError) - end - + it "raises TypeError if the argument cannot be coerced to BigDecimal" do + -> { + @one.divmod('1') + }.should raise_error(TypeError) end + end diff --git a/spec/ruby/library/bigdecimal/double_fig_spec.rb b/spec/ruby/library/bigdecimal/double_fig_spec.rb index d008dbcefb1bf7..f742d68f876f0a 100644 --- a/spec/ruby/library/bigdecimal/double_fig_spec.rb +++ b/spec/ruby/library/bigdecimal/double_fig_spec.rb @@ -1,12 +1,9 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal.double_fig" do - # The result depends on the CPU and OS - it "returns the number of digits a Float number is allowed to have" do - BigDecimal.double_fig.should_not == nil - end +describe "BigDecimal.double_fig" do + # The result depends on the CPU and OS + it "returns the number of digits a Float number is allowed to have" do + BigDecimal.double_fig.should_not == nil end end diff --git a/spec/ruby/library/bigdecimal/dup_spec.rb b/spec/ruby/library/bigdecimal/dup_spec.rb index 3fe07c3738ff32..bfabaf6e8b7c29 100644 --- a/spec/ruby/library/bigdecimal/dup_spec.rb +++ b/spec/ruby/library/bigdecimal/dup_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require_relative 'shared/clone' -ruby_version_is ""..."3.4" do - require_relative 'shared/clone' - - describe "BigDecimal#dup" do - it_behaves_like :bigdecimal_clone, :dup - end +describe "BigDecimal#dup" do + it_behaves_like :bigdecimal_clone, :dup end diff --git a/spec/ruby/library/bigdecimal/eql_spec.rb b/spec/ruby/library/bigdecimal/eql_spec.rb index 4a2c0e0b11926d..1be58627519f17 100644 --- a/spec/ruby/library/bigdecimal/eql_spec.rb +++ b/spec/ruby/library/bigdecimal/eql_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require_relative 'shared/eql' -ruby_version_is ""..."3.4" do - require_relative 'shared/eql' - - describe "BigDecimal#eql?" do - it_behaves_like :bigdecimal_eql, :eql? - end +describe "BigDecimal#eql?" do + it_behaves_like :bigdecimal_eql, :eql? end diff --git a/spec/ruby/library/bigdecimal/equal_value_spec.rb b/spec/ruby/library/bigdecimal/equal_value_spec.rb index 7c2230cbc41795..933060eada8ce8 100644 --- a/spec/ruby/library/bigdecimal/equal_value_spec.rb +++ b/spec/ruby/library/bigdecimal/equal_value_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require_relative 'shared/eql' -ruby_version_is ""..."3.4" do - require_relative 'shared/eql' - - describe "BigDecimal#==" do - it_behaves_like :bigdecimal_eql, :== - end +describe "BigDecimal#==" do + it_behaves_like :bigdecimal_eql, :== end diff --git a/spec/ruby/library/bigdecimal/exponent_spec.rb b/spec/ruby/library/bigdecimal/exponent_spec.rb index 7574e8c560c6cc..887714795514d1 100644 --- a/spec/ruby/library/bigdecimal/exponent_spec.rb +++ b/spec/ruby/library/bigdecimal/exponent_spec.rb @@ -1,30 +1,27 @@ require_relative '../../spec_helper' +require_relative 'shared/power' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/power' - require 'bigdecimal' - - describe "BigDecimal#**" do - it_behaves_like :bigdecimal_power, :** - end - - describe "BigDecimal#exponent" do +describe "BigDecimal#**" do + it_behaves_like :bigdecimal_power, :** +end - it "returns an Integer" do - BigDecimal("2E100000000").exponent.kind_of?(Integer).should == true - BigDecimal("2E-999").exponent.kind_of?(Integer).should == true - end +describe "BigDecimal#exponent" do - it "is n if number can be represented as 0.xxx*10**n" do - BigDecimal("2E1000").exponent.should == 1001 - BigDecimal("1234567E10").exponent.should == 17 - end + it "returns an Integer" do + BigDecimal("2E100000000").exponent.kind_of?(Integer).should == true + BigDecimal("2E-999").exponent.kind_of?(Integer).should == true + end - it "returns 0 if self is 0" do - BigDecimal("0").exponent.should == 0 - BigDecimal("+0").exponent.should == 0 - BigDecimal("-0").exponent.should == 0 - end + it "is n if number can be represented as 0.xxx*10**n" do + BigDecimal("2E1000").exponent.should == 1001 + BigDecimal("1234567E10").exponent.should == 17 + end + it "returns 0 if self is 0" do + BigDecimal("0").exponent.should == 0 + BigDecimal("+0").exponent.should == 0 + BigDecimal("-0").exponent.should == 0 end + end diff --git a/spec/ruby/library/bigdecimal/finite_spec.rb b/spec/ruby/library/bigdecimal/finite_spec.rb index af693ac496337e..8fc06029bb3d76 100644 --- a/spec/ruby/library/bigdecimal/finite_spec.rb +++ b/spec/ruby/library/bigdecimal/finite_spec.rb @@ -1,37 +1,34 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#finite?" do - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - @big = BigDecimal("2E40001") - @finite_vals = [@one, @zero, @zero_pos, @zero_neg, @two, - @three, @frac_1, @frac_2, @big, @one_minus] - end +describe "BigDecimal#finite?" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @big = BigDecimal("2E40001") + @finite_vals = [@one, @zero, @zero_pos, @zero_neg, @two, + @three, @frac_1, @frac_2, @big, @one_minus] + end - it "is false if Infinity or NaN" do - @infinity.should_not.finite? - @infinity_minus.should_not.finite? - @nan.should_not.finite? - end + it "is false if Infinity or NaN" do + @infinity.should_not.finite? + @infinity_minus.should_not.finite? + @nan.should_not.finite? + end - it "returns true for finite values" do - @finite_vals.each do |val| - val.should.finite? - end + it "returns true for finite values" do + @finite_vals.each do |val| + val.should.finite? end end end diff --git a/spec/ruby/library/bigdecimal/fix_spec.rb b/spec/ruby/library/bigdecimal/fix_spec.rb index 49a898b9c5e716..231c9a587e3cae 100644 --- a/spec/ruby/library/bigdecimal/fix_spec.rb +++ b/spec/ruby/library/bigdecimal/fix_spec.rb @@ -1,60 +1,57 @@ require_relative '../../spec_helper' - -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#fix" do - before :each do - @zero = BigDecimal("0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - end - - it "returns a BigDecimal" do - BigDecimal("2E100000000").fix.kind_of?(BigDecimal).should == true - BigDecimal("2E-999").kind_of?(BigDecimal).should == true +require 'bigdecimal' + +describe "BigDecimal#fix" do + before :each do + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") end - it "returns the integer part of the absolute value" do - a = BigDecimal("2E1000") - a.fix.should == a - b = BigDecimal("-2E1000") - b.fix.should == b - BigDecimal("0.123456789E5").fix.should == BigDecimal("0.12345E5") - BigDecimal("-0.123456789E5").fix.should == BigDecimal("-0.12345E5") - end + it "returns a BigDecimal" do + BigDecimal("2E100000000").fix.kind_of?(BigDecimal).should == true + BigDecimal("2E-999").kind_of?(BigDecimal).should == true + end - it "correctly handles special values" do - @infinity.fix.should == @infinity - @infinity_neg.fix.should == @infinity_neg - @nan.fix.should.nan? - end + it "returns the integer part of the absolute value" do + a = BigDecimal("2E1000") + a.fix.should == a + b = BigDecimal("-2E1000") + b.fix.should == b + BigDecimal("0.123456789E5").fix.should == BigDecimal("0.12345E5") + BigDecimal("-0.123456789E5").fix.should == BigDecimal("-0.12345E5") + end - it "returns 0 if the absolute value is < 1" do - BigDecimal("0.99999").fix.should == 0 - BigDecimal("-0.99999").fix.should == 0 - BigDecimal("0.000000001").fix.should == 0 - BigDecimal("-0.00000001").fix.should == 0 - BigDecimal("-1000000").fix.should_not == 0 - @zero.fix.should == 0 - @zero_pos.fix.should == @zero_pos - @zero_neg.fix.should == @zero_neg - end + it "correctly handles special values" do + @infinity.fix.should == @infinity + @infinity_neg.fix.should == @infinity_neg + @nan.fix.should.nan? + end - it "does not allow any arguments" do - -> { - @mixed.fix(10) - }.should raise_error(ArgumentError) - end + it "returns 0 if the absolute value is < 1" do + BigDecimal("0.99999").fix.should == 0 + BigDecimal("-0.99999").fix.should == 0 + BigDecimal("0.000000001").fix.should == 0 + BigDecimal("-0.00000001").fix.should == 0 + BigDecimal("-1000000").fix.should_not == 0 + @zero.fix.should == 0 + @zero_pos.fix.should == @zero_pos + @zero_neg.fix.should == @zero_neg + end + it "does not allow any arguments" do + -> { + @mixed.fix(10) + }.should raise_error(ArgumentError) end + end diff --git a/spec/ruby/library/bigdecimal/floor_spec.rb b/spec/ruby/library/bigdecimal/floor_spec.rb index e36843c1167c5c..a7dfec2c9a1fcb 100644 --- a/spec/ruby/library/bigdecimal/floor_spec.rb +++ b/spec/ruby/library/bigdecimal/floor_spec.rb @@ -1,103 +1,100 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#floor" do + before :each do + @one = BigDecimal("1") + @three = BigDecimal("3") + @four = BigDecimal("4") + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @mixed_big = BigDecimal("1.23456789E100") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") - describe "BigDecimal#floor" do - before :each do - @one = BigDecimal("1") - @three = BigDecimal("3") - @four = BigDecimal("4") - @zero = BigDecimal("0") - @mixed = BigDecimal("1.23456789") - @mixed_big = BigDecimal("1.23456789E100") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - end + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end - it "returns the greatest integer smaller or equal to self" do - @pos_int.floor.should == @pos_int - @neg_int.floor.should == @neg_int - @pos_frac.floor.should == @zero - @neg_frac.floor.should == BigDecimal("-1") - @zero.floor.should == 0 - @zero_pos.floor.should == @zero_pos - @zero_neg.floor.should == @zero_neg + it "returns the greatest integer smaller or equal to self" do + @pos_int.floor.should == @pos_int + @neg_int.floor.should == @neg_int + @pos_frac.floor.should == @zero + @neg_frac.floor.should == BigDecimal("-1") + @zero.floor.should == 0 + @zero_pos.floor.should == @zero_pos + @zero_neg.floor.should == @zero_neg - BigDecimal('2.3').floor.should == 2 - BigDecimal('2.5').floor.should == 2 - BigDecimal('2.9999').floor.should == 2 - BigDecimal('-2.3').floor.should == -3 - BigDecimal('-2.5').floor.should == -3 - BigDecimal('-2.9999').floor.should == -3 - BigDecimal('0.8').floor.should == 0 - BigDecimal('-0.8').floor.should == -1 - end + BigDecimal('2.3').floor.should == 2 + BigDecimal('2.5').floor.should == 2 + BigDecimal('2.9999').floor.should == 2 + BigDecimal('-2.3').floor.should == -3 + BigDecimal('-2.5').floor.should == -3 + BigDecimal('-2.9999').floor.should == -3 + BigDecimal('0.8').floor.should == 0 + BigDecimal('-0.8').floor.should == -1 + end - it "raise exception, if self is special value" do - -> { @infinity.floor }.should raise_error(FloatDomainError) - -> { @infinity_neg.floor }.should raise_error(FloatDomainError) - -> { @nan.floor }.should raise_error(FloatDomainError) - end + it "raise exception, if self is special value" do + -> { @infinity.floor }.should raise_error(FloatDomainError) + -> { @infinity_neg.floor }.should raise_error(FloatDomainError) + -> { @nan.floor }.should raise_error(FloatDomainError) + end - it "returns n digits right of the decimal point if given n > 0" do - @mixed.floor(1).should == BigDecimal("1.2") - @mixed.floor(5).should == BigDecimal("1.23456") + it "returns n digits right of the decimal point if given n > 0" do + @mixed.floor(1).should == BigDecimal("1.2") + @mixed.floor(5).should == BigDecimal("1.23456") - BigDecimal("-0.03").floor(1).should == BigDecimal("-0.1") - BigDecimal("0.03").floor(1).should == BigDecimal("0") + BigDecimal("-0.03").floor(1).should == BigDecimal("-0.1") + BigDecimal("0.03").floor(1).should == BigDecimal("0") - BigDecimal("23.45").floor(0).should == BigDecimal('23') - BigDecimal("23.45").floor(1).should == BigDecimal('23.4') - BigDecimal("23.45").floor(2).should == BigDecimal('23.45') + BigDecimal("23.45").floor(0).should == BigDecimal('23') + BigDecimal("23.45").floor(1).should == BigDecimal('23.4') + BigDecimal("23.45").floor(2).should == BigDecimal('23.45') - BigDecimal("-23.45").floor(0).should == BigDecimal('-24') - BigDecimal("-23.45").floor(1).should == BigDecimal('-23.5') - BigDecimal("-23.45").floor(2).should == BigDecimal('-23.45') + BigDecimal("-23.45").floor(0).should == BigDecimal('-24') + BigDecimal("-23.45").floor(1).should == BigDecimal('-23.5') + BigDecimal("-23.45").floor(2).should == BigDecimal('-23.45') - BigDecimal("2E-10").floor(0).should == @zero - BigDecimal("2E-10").floor(9).should == @zero - BigDecimal("2E-10").floor(10).should == BigDecimal('2E-10') - BigDecimal("2E-10").floor(11).should == BigDecimal('2E-10') + BigDecimal("2E-10").floor(0).should == @zero + BigDecimal("2E-10").floor(9).should == @zero + BigDecimal("2E-10").floor(10).should == BigDecimal('2E-10') + BigDecimal("2E-10").floor(11).should == BigDecimal('2E-10') - (1..10).each do |n| - # 0.3, 0.33, 0.333, etc. - (@one.div(@three,20)).floor(n).should == BigDecimal("0.#{'3'*n}") - # 1.3, 1.33, 1.333, etc. - (@four.div(@three,20)).floor(n).should == BigDecimal("1.#{'3'*n}") - (BigDecimal('31').div(@three,20)).floor(n).should == BigDecimal("10.#{'3'*n}") - end - (1..10).each do |n| - # -0.4, -0.34, -0.334, etc. - (-@one.div(@three,20)).floor(n).should == BigDecimal("-0.#{'3'*(n-1)}4") - end - (1..10).each do |n| - (@three.div(@one,20)).floor(n).should == @three - end - (1..10).each do |n| - (-@three.div(@one,20)).floor(n).should == -@three - end + (1..10).each do |n| + # 0.3, 0.33, 0.333, etc. + (@one.div(@three,20)).floor(n).should == BigDecimal("0.#{'3'*n}") + # 1.3, 1.33, 1.333, etc. + (@four.div(@three,20)).floor(n).should == BigDecimal("1.#{'3'*n}") + (BigDecimal('31').div(@three,20)).floor(n).should == BigDecimal("10.#{'3'*n}") end - - it "sets n digits left of the decimal point to 0, if given n < 0" do - BigDecimal("13345.234").floor(-2).should == BigDecimal("13300.0") - @mixed_big.floor(-99).should == BigDecimal("0.12E101") - @mixed_big.floor(-100).should == BigDecimal("0.1E101") - @mixed_big.floor(-95).should == BigDecimal("0.123456E101") - (1..10).each do |n| - BigDecimal('1.8').floor(-n).should == @zero - end - BigDecimal("1E10").floor(-30).should == @zero - BigDecimal("-1E10").floor(-30).should == BigDecimal('-1E30') + (1..10).each do |n| + # -0.4, -0.34, -0.334, etc. + (-@one.div(@three,20)).floor(n).should == BigDecimal("-0.#{'3'*(n-1)}4") + end + (1..10).each do |n| + (@three.div(@one,20)).floor(n).should == @three + end + (1..10).each do |n| + (-@three.div(@one,20)).floor(n).should == -@three end + end + it "sets n digits left of the decimal point to 0, if given n < 0" do + BigDecimal("13345.234").floor(-2).should == BigDecimal("13300.0") + @mixed_big.floor(-99).should == BigDecimal("0.12E101") + @mixed_big.floor(-100).should == BigDecimal("0.1E101") + @mixed_big.floor(-95).should == BigDecimal("0.123456E101") + (1..10).each do |n| + BigDecimal('1.8').floor(-n).should == @zero + end + BigDecimal("1E10").floor(-30).should == @zero + BigDecimal("-1E10").floor(-30).should == BigDecimal('-1E30') end + end diff --git a/spec/ruby/library/bigdecimal/frac_spec.rb b/spec/ruby/library/bigdecimal/frac_spec.rb index 9cf72d313ec78f..11ccf03c2f0606 100644 --- a/spec/ruby/library/bigdecimal/frac_spec.rb +++ b/spec/ruby/library/bigdecimal/frac_spec.rb @@ -1,51 +1,48 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#frac" do + before :each do + @zero = BigDecimal("0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#frac" do - before :each do - @zero = BigDecimal("0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - end - - it "returns a BigDecimal" do - @pos_int.frac.kind_of?(BigDecimal).should == true - @neg_int.frac.kind_of?(BigDecimal).should == true - @pos_frac.kind_of?(BigDecimal).should == true - @neg_frac.kind_of?(BigDecimal).should == true - end - - it "returns the fractional part of the absolute value" do - @mixed.frac.should == BigDecimal("0.23456789") - @pos_frac.frac.should == @pos_frac - @neg_frac.frac.should == @neg_frac - end - - it "returns 0 if the value is 0" do - @zero.frac.should == @zero - end - - it "returns 0 if the value is an integer" do - @pos_int.frac.should == @zero - @neg_int.frac.should == @zero - end - - it "correctly handles special values" do - @infinity.frac.should == @infinity - @infinity_neg.frac.should == @infinity_neg - @nan.frac.should.nan? - end + it "returns a BigDecimal" do + @pos_int.frac.kind_of?(BigDecimal).should == true + @neg_int.frac.kind_of?(BigDecimal).should == true + @pos_frac.kind_of?(BigDecimal).should == true + @neg_frac.kind_of?(BigDecimal).should == true + end + it "returns the fractional part of the absolute value" do + @mixed.frac.should == BigDecimal("0.23456789") + @pos_frac.frac.should == @pos_frac + @neg_frac.frac.should == @neg_frac end + + it "returns 0 if the value is 0" do + @zero.frac.should == @zero + end + + it "returns 0 if the value is an integer" do + @pos_int.frac.should == @zero + @neg_int.frac.should == @zero + end + + it "correctly handles special values" do + @infinity.frac.should == @infinity + @infinity_neg.frac.should == @infinity_neg + @nan.frac.should.nan? + end + end diff --git a/spec/ruby/library/bigdecimal/gt_spec.rb b/spec/ruby/library/bigdecimal/gt_spec.rb index 2af84673d382f7..78547fb85f152f 100644 --- a/spec/ruby/library/bigdecimal/gt_spec.rb +++ b/spec/ruby/library/bigdecimal/gt_spec.rb @@ -1,99 +1,96 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#>" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") - describe "BigDecimal#>" do - before :each do - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @int_mock = mock('123') - class << @int_mock - def coerce(other) - return [other, BigDecimal('123')] - end - def >(other) - BigDecimal('123') > other - end + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >(other) + BigDecimal('123') > other end + end - @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, - -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, - @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") - @float_infinity = Float::INFINITY - @float_infinity_neg = -Float::INFINITY + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY - @nan = BigDecimal("NaN") - end + @nan = BigDecimal("NaN") + end - it "returns true if a > b" do - one = BigDecimal("1") - two = BigDecimal("2") + it "returns true if a > b" do + one = BigDecimal("1") + two = BigDecimal("2") - frac_1 = BigDecimal("1E-99999") - frac_2 = BigDecimal("0.9E-99999") - (@zero > one).should == false - (two > @zero).should == true - (frac_2 > frac_1).should == false + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + (@zero > one).should == false + (two > @zero).should == true + (frac_2 > frac_1).should == false - (@neg_int > @pos_int).should == false - (@pos_int > @neg_int).should == true - (@neg_int > @pos_frac).should == false - (@pos_frac > @neg_int).should == true - (@zero > @zero_pos).should == false - (@zero > @zero_neg).should == false - (@zero_neg > @zero_pos).should == false - (@zero_pos > @zero_neg).should == false - end + (@neg_int > @pos_int).should == false + (@pos_int > @neg_int).should == true + (@neg_int > @pos_frac).should == false + (@pos_frac > @neg_int).should == true + (@zero > @zero_pos).should == false + (@zero > @zero_neg).should == false + (@zero_neg > @zero_pos).should == false + (@zero_pos > @zero_neg).should == false + end - it "properly handles infinity values" do - @values.each { |val| - (val > @infinity).should == false - (@infinity > val).should == true - (val > @infinity_neg).should == true - (@infinity_neg > val).should == false - } - (@infinity > @infinity).should == false - (@infinity_neg > @infinity_neg).should == false - (@infinity > @infinity_neg).should == true - (@infinity_neg > @infinity).should == false - end + it "properly handles infinity values" do + @values.each { |val| + (val > @infinity).should == false + (@infinity > val).should == true + (val > @infinity_neg).should == true + (@infinity_neg > val).should == false + } + (@infinity > @infinity).should == false + (@infinity_neg > @infinity_neg).should == false + (@infinity > @infinity_neg).should == true + (@infinity_neg > @infinity).should == false + end - it "properly handles Float infinity values" do - @values.each { |val| - (val > @float_infinity).should == false - (@float_infinity > val).should == true - (val > @float_infinity_neg).should == true - (@float_infinity_neg > val).should == false - } - end + it "properly handles Float infinity values" do + @values.each { |val| + (val > @float_infinity).should == false + (@float_infinity > val).should == true + (val > @float_infinity_neg).should == true + (@float_infinity_neg > val).should == false + } + end - it "properly handles NaN values" do - @values += [@infinity, @infinity_neg, @nan] - @values.each { |val| - (@nan > val).should == false - (val > @nan).should == false - } - end + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan > val).should == false + (val > @nan).should == false + } + end - it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do - -> {@zero > nil }.should raise_error(ArgumentError) - -> {@infinity > nil }.should raise_error(ArgumentError) - -> {@infinity_neg > nil }.should raise_error(ArgumentError) - -> {@mixed > nil }.should raise_error(ArgumentError) - -> {@pos_int > nil }.should raise_error(ArgumentError) - -> {@neg_frac > nil }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero > nil }.should raise_error(ArgumentError) + -> {@infinity > nil }.should raise_error(ArgumentError) + -> {@infinity_neg > nil }.should raise_error(ArgumentError) + -> {@mixed > nil }.should raise_error(ArgumentError) + -> {@pos_int > nil }.should raise_error(ArgumentError) + -> {@neg_frac > nil }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/bigdecimal/gte_spec.rb b/spec/ruby/library/bigdecimal/gte_spec.rb index 1f9838bf25b434..2a5cc025ba3821 100644 --- a/spec/ruby/library/bigdecimal/gte_spec.rb +++ b/spec/ruby/library/bigdecimal/gte_spec.rb @@ -1,103 +1,100 @@ require_relative '../../spec_helper' - -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#>=" do - before :each do - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @int_mock = mock('123') - class << @int_mock - def coerce(other) - return [other, BigDecimal('123')] - end - def >=(other) - BigDecimal('123') >= other - end +require 'bigdecimal' + +describe "BigDecimal#>=" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def >=(other) + BigDecimal('123') >= other end + end - @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, - -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, - @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") - @float_infinity = Float::INFINITY - @float_infinity_neg = -Float::INFINITY + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY - @nan = BigDecimal("NaN") - end + @nan = BigDecimal("NaN") + end - it "returns true if a >= b" do - one = BigDecimal("1") - two = BigDecimal("2") + it "returns true if a >= b" do + one = BigDecimal("1") + two = BigDecimal("2") - frac_1 = BigDecimal("1E-99999") - frac_2 = BigDecimal("0.9E-99999") + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") - (@zero >= one).should == false - (two >= @zero).should == true + (@zero >= one).should == false + (two >= @zero).should == true - (frac_2 >= frac_1).should == false - (two >= two).should == true - (frac_1 >= frac_1).should == true + (frac_2 >= frac_1).should == false + (two >= two).should == true + (frac_1 >= frac_1).should == true - (@neg_int >= @pos_int).should == false - (@pos_int >= @neg_int).should == true - (@neg_int >= @pos_frac).should == false - (@pos_frac >= @neg_int).should == true - (@zero >= @zero_pos).should == true - (@zero >= @zero_neg).should == true - (@zero_neg >= @zero_pos).should == true - (@zero_pos >= @zero_neg).should == true - end + (@neg_int >= @pos_int).should == false + (@pos_int >= @neg_int).should == true + (@neg_int >= @pos_frac).should == false + (@pos_frac >= @neg_int).should == true + (@zero >= @zero_pos).should == true + (@zero >= @zero_neg).should == true + (@zero_neg >= @zero_pos).should == true + (@zero_pos >= @zero_neg).should == true + end - it "properly handles infinity values" do - @values.each { |val| - (val >= @infinity).should == false - (@infinity >= val).should == true - (val >= @infinity_neg).should == true - (@infinity_neg >= val).should == false - } - (@infinity >= @infinity).should == true - (@infinity_neg >= @infinity_neg).should == true - (@infinity >= @infinity_neg).should == true - (@infinity_neg >= @infinity).should == false - end + it "properly handles infinity values" do + @values.each { |val| + (val >= @infinity).should == false + (@infinity >= val).should == true + (val >= @infinity_neg).should == true + (@infinity_neg >= val).should == false + } + (@infinity >= @infinity).should == true + (@infinity_neg >= @infinity_neg).should == true + (@infinity >= @infinity_neg).should == true + (@infinity_neg >= @infinity).should == false + end - it "properly handles Float infinity values" do - @values.each { |val| - (val >= @float_infinity).should == false - (@float_infinity >= val).should == true - (val >= @float_infinity_neg).should == true - (@float_infinity_neg >= val).should == false - } - end + it "properly handles Float infinity values" do + @values.each { |val| + (val >= @float_infinity).should == false + (@float_infinity >= val).should == true + (val >= @float_infinity_neg).should == true + (@float_infinity_neg >= val).should == false + } + end - it "properly handles NaN values" do - @values += [@infinity, @infinity_neg, @nan] - @values.each { |val| - (@nan >= val).should == false - (val >= @nan).should == false - } - end + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan >= val).should == false + (val >= @nan).should == false + } + end - it "returns nil if the argument is nil" do - -> {@zero >= nil }.should raise_error(ArgumentError) - -> {@infinity >= nil }.should raise_error(ArgumentError) - -> {@infinity_neg >= nil }.should raise_error(ArgumentError) - -> {@mixed >= nil }.should raise_error(ArgumentError) - -> {@pos_int >= nil }.should raise_error(ArgumentError) - -> {@neg_frac >= nil }.should raise_error(ArgumentError) - end + it "returns nil if the argument is nil" do + -> {@zero >= nil }.should raise_error(ArgumentError) + -> {@infinity >= nil }.should raise_error(ArgumentError) + -> {@infinity_neg >= nil }.should raise_error(ArgumentError) + -> {@mixed >= nil }.should raise_error(ArgumentError) + -> {@pos_int >= nil }.should raise_error(ArgumentError) + -> {@neg_frac >= nil }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/bigdecimal/hash_spec.rb b/spec/ruby/library/bigdecimal/hash_spec.rb index 7eea0f6464d72f..7581c90f681bee 100644 --- a/spec/ruby/library/bigdecimal/hash_spec.rb +++ b/spec/ruby/library/bigdecimal/hash_spec.rb @@ -1,33 +1,30 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BidDecimal#hash" do - describe "two BigDecimal objects with the same value" do - it "should have the same hash for ordinary values" do - BigDecimal('1.2920').hash.should == BigDecimal('1.2920').hash - end +describe "BidDecimal#hash" do + describe "two BigDecimal objects with the same value" do + it "should have the same hash for ordinary values" do + BigDecimal('1.2920').hash.should == BigDecimal('1.2920').hash + end - it "should have the same hash for infinite values" do - BigDecimal("+Infinity").hash.should == BigDecimal("+Infinity").hash - BigDecimal("-Infinity").hash.should == BigDecimal("-Infinity").hash - end + it "should have the same hash for infinite values" do + BigDecimal("+Infinity").hash.should == BigDecimal("+Infinity").hash + BigDecimal("-Infinity").hash.should == BigDecimal("-Infinity").hash + end - it "should have the same hash for NaNs" do - BigDecimal("NaN").hash.should == BigDecimal("NaN").hash - end + it "should have the same hash for NaNs" do + BigDecimal("NaN").hash.should == BigDecimal("NaN").hash + end - it "should have the same hash for zero values" do - BigDecimal("+0").hash.should == BigDecimal("+0").hash - BigDecimal("-0").hash.should == BigDecimal("-0").hash - end + it "should have the same hash for zero values" do + BigDecimal("+0").hash.should == BigDecimal("+0").hash + BigDecimal("-0").hash.should == BigDecimal("-0").hash end + end - describe "two BigDecimal objects with numerically equal values" do - it "should have the same hash value" do - BigDecimal("1.2920").hash.should == BigDecimal("1.2920000").hash - end + describe "two BigDecimal objects with numerically equal values" do + it "should have the same hash value" do + BigDecimal("1.2920").hash.should == BigDecimal("1.2920000").hash end end end diff --git a/spec/ruby/library/bigdecimal/infinite_spec.rb b/spec/ruby/library/bigdecimal/infinite_spec.rb index 79a451d85771b0..025386107f668c 100644 --- a/spec/ruby/library/bigdecimal/infinite_spec.rb +++ b/spec/ruby/library/bigdecimal/infinite_spec.rb @@ -1,35 +1,32 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#infinite?" do - describe "BigDecimal#infinite?" do - - it "returns 1 if self is Infinity" do - BigDecimal("Infinity").infinite?.should == 1 - end - - it "returns -1 if self is -Infinity" do - BigDecimal("-Infinity").infinite?.should == -1 - end + it "returns 1 if self is Infinity" do + BigDecimal("Infinity").infinite?.should == 1 + end - it "returns not true otherwise" do - e2_plus = BigDecimal("2E40001") - e3_minus = BigDecimal("3E-20001") - really_small_zero = BigDecimal("0E-200000000") - really_big_zero = BigDecimal("0E200000000000") - e3_minus.infinite?.should == nil - e2_plus.infinite?.should == nil - really_small_zero.infinite?.should == nil - really_big_zero.infinite?.should == nil - BigDecimal("0.000000000000000000000000").infinite?.should == nil - end + it "returns -1 if self is -Infinity" do + BigDecimal("-Infinity").infinite?.should == -1 + end - it "returns not true if self is NaN" do - # NaN is a special value which is neither finite nor infinite. - nan = BigDecimal("NaN") - nan.infinite?.should == nil - end + it "returns not true otherwise" do + e2_plus = BigDecimal("2E40001") + e3_minus = BigDecimal("3E-20001") + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + e3_minus.infinite?.should == nil + e2_plus.infinite?.should == nil + really_small_zero.infinite?.should == nil + really_big_zero.infinite?.should == nil + BigDecimal("0.000000000000000000000000").infinite?.should == nil + end + it "returns not true if self is NaN" do + # NaN is a special value which is neither finite nor infinite. + nan = BigDecimal("NaN") + nan.infinite?.should == nil end + end diff --git a/spec/ruby/library/bigdecimal/inspect_spec.rb b/spec/ruby/library/bigdecimal/inspect_spec.rb index a6f72f759f301a..7ce47142b2b603 100644 --- a/spec/ruby/library/bigdecimal/inspect_spec.rb +++ b/spec/ruby/library/bigdecimal/inspect_spec.rb @@ -1,33 +1,30 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#inspect" do - describe "BigDecimal#inspect" do - - before :each do - @bigdec = BigDecimal("1234.5678") - end + before :each do + @bigdec = BigDecimal("1234.5678") + end - it "returns String" do - @bigdec.inspect.kind_of?(String).should == true - end + it "returns String" do + @bigdec.inspect.kind_of?(String).should == true + end - it "looks like this" do - @bigdec.inspect.should == "0.12345678e4" - end + it "looks like this" do + @bigdec.inspect.should == "0.12345678e4" + end - it "does not add an exponent for zero values" do - BigDecimal("0").inspect.should == "0.0" - BigDecimal("+0").inspect.should == "0.0" - BigDecimal("-0").inspect.should == "-0.0" - end + it "does not add an exponent for zero values" do + BigDecimal("0").inspect.should == "0.0" + BigDecimal("+0").inspect.should == "0.0" + BigDecimal("-0").inspect.should == "-0.0" + end - it "properly cases non-finite values" do - BigDecimal("NaN").inspect.should == "NaN" - BigDecimal("Infinity").inspect.should == "Infinity" - BigDecimal("+Infinity").inspect.should == "Infinity" - BigDecimal("-Infinity").inspect.should == "-Infinity" - end + it "properly cases non-finite values" do + BigDecimal("NaN").inspect.should == "NaN" + BigDecimal("Infinity").inspect.should == "Infinity" + BigDecimal("+Infinity").inspect.should == "Infinity" + BigDecimal("-Infinity").inspect.should == "-Infinity" end end diff --git a/spec/ruby/library/bigdecimal/limit_spec.rb b/spec/ruby/library/bigdecimal/limit_spec.rb index c1d130d4ec525c..75cbc8b55c9f0c 100644 --- a/spec/ruby/library/bigdecimal/limit_spec.rb +++ b/spec/ruby/library/bigdecimal/limit_spec.rb @@ -1,58 +1,55 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'bigdecimal' + +describe "BigDecimal.limit" do + it "returns the value before set if the passed argument is nil or is not specified" do + old = BigDecimal.limit + BigDecimal.limit.should == 0 + BigDecimal.limit(10).should == 0 + BigDecimal.limit.should == 10 + BigDecimal.limit(old) + end -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - require 'bigdecimal' - - describe "BigDecimal.limit" do - it "returns the value before set if the passed argument is nil or is not specified" do - old = BigDecimal.limit - BigDecimal.limit.should == 0 - BigDecimal.limit(10).should == 0 - BigDecimal.limit.should == 10 - BigDecimal.limit(old) + it "uses the global limit if no precision is specified" do + BigDecimalSpecs.with_limit(0) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.888') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.888') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.664') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.296') end - it "uses the global limit if no precision is specified" do - BigDecimalSpecs.with_limit(0) do - (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.888') - (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.888') - (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.664') - (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.296') - end - - BigDecimalSpecs.with_limit(1) do - (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.9') - (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.9') - (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('3') - (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.3') - end - - BigDecimalSpecs.with_limit(2) do - (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.89') - (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.89') - (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.7') - (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.30') - end + BigDecimalSpecs.with_limit(1) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.9') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.9') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('3') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.3') end - it "picks the specified precision over global limit" do - BigDecimalSpecs.with_limit(3) do - BigDecimal('0.888').add(BigDecimal('0'), 2).should == BigDecimal('0.89') - BigDecimal('0.888').sub(BigDecimal('0'), 2).should == BigDecimal('0.89') - BigDecimal('0.888').mult(BigDecimal('3'), 2).should == BigDecimal('2.7') - BigDecimal('0.888').div(BigDecimal('3'), 2).should == BigDecimal('0.30') - end + BigDecimalSpecs.with_limit(2) do + (BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.89') + (BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.89') + (BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.7') + (BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.30') end + end - it "picks the global precision when limit 0 specified" do - BigDecimalSpecs.with_limit(3) do - BigDecimal('0.8888').add(BigDecimal('0'), 0).should == BigDecimal('0.889') - BigDecimal('0.8888').sub(BigDecimal('0'), 0).should == BigDecimal('0.889') - BigDecimal('0.888').mult(BigDecimal('3'), 0).should == BigDecimal('2.66') - BigDecimal('0.8888').div(BigDecimal('3'), 0).should == BigDecimal('0.296') - end + it "picks the specified precision over global limit" do + BigDecimalSpecs.with_limit(3) do + BigDecimal('0.888').add(BigDecimal('0'), 2).should == BigDecimal('0.89') + BigDecimal('0.888').sub(BigDecimal('0'), 2).should == BigDecimal('0.89') + BigDecimal('0.888').mult(BigDecimal('3'), 2).should == BigDecimal('2.7') + BigDecimal('0.888').div(BigDecimal('3'), 2).should == BigDecimal('0.30') end + end + it "picks the global precision when limit 0 specified" do + BigDecimalSpecs.with_limit(3) do + BigDecimal('0.8888').add(BigDecimal('0'), 0).should == BigDecimal('0.889') + BigDecimal('0.8888').sub(BigDecimal('0'), 0).should == BigDecimal('0.889') + BigDecimal('0.888').mult(BigDecimal('3'), 0).should == BigDecimal('2.66') + BigDecimal('0.8888').div(BigDecimal('3'), 0).should == BigDecimal('0.296') + end end + end diff --git a/spec/ruby/library/bigdecimal/lt_spec.rb b/spec/ruby/library/bigdecimal/lt_spec.rb index 7cffb12f7b9085..02390e76e33b61 100644 --- a/spec/ruby/library/bigdecimal/lt_spec.rb +++ b/spec/ruby/library/bigdecimal/lt_spec.rb @@ -1,97 +1,94 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#<" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") - describe "BigDecimal#<" do - before :each do - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @int_mock = mock('123') - class << @int_mock - def coerce(other) - return [other, BigDecimal('123')] - end - def <(other) - BigDecimal('123') < other - end + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def <(other) + BigDecimal('123') < other end + end - @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, - -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, - @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") - @float_infinity = Float::INFINITY - @float_infinity_neg = -Float::INFINITY + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY - @nan = BigDecimal("NaN") - end + @nan = BigDecimal("NaN") + end - it "returns true if a < b" do - one = BigDecimal("1") - two = BigDecimal("2") - frac_1 = BigDecimal("1E-99999") - frac_2 = BigDecimal("0.9E-99999") - (@zero < one).should == true - (two < @zero).should == false - (frac_2 < frac_1).should == true - (@neg_int < @pos_int).should == true - (@pos_int < @neg_int).should == false - (@neg_int < @pos_frac).should == true - (@pos_frac < @neg_int).should == false - (@zero < @zero_pos).should == false - (@zero < @zero_neg).should == false - (@zero_neg < @zero_pos).should == false - (@zero_pos < @zero_neg).should == false - end + it "returns true if a < b" do + one = BigDecimal("1") + two = BigDecimal("2") + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") + (@zero < one).should == true + (two < @zero).should == false + (frac_2 < frac_1).should == true + (@neg_int < @pos_int).should == true + (@pos_int < @neg_int).should == false + (@neg_int < @pos_frac).should == true + (@pos_frac < @neg_int).should == false + (@zero < @zero_pos).should == false + (@zero < @zero_neg).should == false + (@zero_neg < @zero_pos).should == false + (@zero_pos < @zero_neg).should == false + end - it "properly handles infinity values" do - @values.each { |val| - (val < @infinity).should == true - (@infinity < val).should == false - (val < @infinity_neg).should == false - (@infinity_neg < val).should == true - } - (@infinity < @infinity).should == false - (@infinity_neg < @infinity_neg).should == false - (@infinity < @infinity_neg).should == false - (@infinity_neg < @infinity).should == true - end + it "properly handles infinity values" do + @values.each { |val| + (val < @infinity).should == true + (@infinity < val).should == false + (val < @infinity_neg).should == false + (@infinity_neg < val).should == true + } + (@infinity < @infinity).should == false + (@infinity_neg < @infinity_neg).should == false + (@infinity < @infinity_neg).should == false + (@infinity_neg < @infinity).should == true + end - it "properly handles Float infinity values" do - @values.each { |val| - (val < @float_infinity).should == true - (@float_infinity < val).should == false - (val < @float_infinity_neg).should == false - (@float_infinity_neg < val).should == true - } - end + it "properly handles Float infinity values" do + @values.each { |val| + (val < @float_infinity).should == true + (@float_infinity < val).should == false + (val < @float_infinity_neg).should == false + (@float_infinity_neg < val).should == true + } + end - it "properly handles NaN values" do - @values += [@infinity, @infinity_neg, @nan] - @values.each { |val| - (@nan < val).should == false - (val < @nan).should == false - } - end + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan < val).should == false + (val < @nan).should == false + } + end - it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do - -> {@zero < nil }.should raise_error(ArgumentError) - -> {@infinity < nil }.should raise_error(ArgumentError) - -> {@infinity_neg < nil }.should raise_error(ArgumentError) - -> {@mixed < nil }.should raise_error(ArgumentError) - -> {@pos_int < nil }.should raise_error(ArgumentError) - -> {@neg_frac < nil }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero < nil }.should raise_error(ArgumentError) + -> {@infinity < nil }.should raise_error(ArgumentError) + -> {@infinity_neg < nil }.should raise_error(ArgumentError) + -> {@mixed < nil }.should raise_error(ArgumentError) + -> {@pos_int < nil }.should raise_error(ArgumentError) + -> {@neg_frac < nil }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/bigdecimal/lte_spec.rb b/spec/ruby/library/bigdecimal/lte_spec.rb index 2dc6bdc9437363..b92be04123f106 100644 --- a/spec/ruby/library/bigdecimal/lte_spec.rb +++ b/spec/ruby/library/bigdecimal/lte_spec.rb @@ -1,103 +1,100 @@ require_relative '../../spec_helper' - -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#<=" do - before :each do - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - - @int_mock = mock('123') - class << @int_mock - def coerce(other) - return [other, BigDecimal('123')] - end - def <=(other) - BigDecimal('123') <= other - end +require 'bigdecimal' + +describe "BigDecimal#<=" do + before :each do + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + + @int_mock = mock('123') + class << @int_mock + def coerce(other) + return [other, BigDecimal('123')] + end + def <=(other) + BigDecimal('123') <= other end + end - @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, - -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, - @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] + @values = [@mixed, @pos_int, @neg_int, @pos_frac, @neg_frac, + -2**32, -2**31, -2**30, -2**16, -2**8, -100, -10, -1, + @zero , 1, 2, 10, 10.5, 2**8, 2**16, 2**32, @int_mock, @zero_pos, @zero_neg] - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") - @float_infinity = Float::INFINITY - @float_infinity_neg = -Float::INFINITY + @float_infinity = Float::INFINITY + @float_infinity_neg = -Float::INFINITY - @nan = BigDecimal("NaN") - end + @nan = BigDecimal("NaN") + end - it "returns true if a <= b" do - one = BigDecimal("1") - two = BigDecimal("2") + it "returns true if a <= b" do + one = BigDecimal("1") + two = BigDecimal("2") - frac_1 = BigDecimal("1E-99999") - frac_2 = BigDecimal("0.9E-99999") + frac_1 = BigDecimal("1E-99999") + frac_2 = BigDecimal("0.9E-99999") - (@zero <= one).should == true - (two <= @zero).should == false + (@zero <= one).should == true + (two <= @zero).should == false - (frac_2 <= frac_1).should == true - (two <= two).should == true - (frac_1 <= frac_1).should == true + (frac_2 <= frac_1).should == true + (two <= two).should == true + (frac_1 <= frac_1).should == true - (@neg_int <= @pos_int).should == true - (@pos_int <= @neg_int).should == false - (@neg_int <= @pos_frac).should == true - (@pos_frac <= @neg_int).should == false - (@zero <= @zero_pos).should == true - (@zero <= @zero_neg).should == true - (@zero_neg <= @zero_pos).should == true - (@zero_pos <= @zero_neg).should == true - end + (@neg_int <= @pos_int).should == true + (@pos_int <= @neg_int).should == false + (@neg_int <= @pos_frac).should == true + (@pos_frac <= @neg_int).should == false + (@zero <= @zero_pos).should == true + (@zero <= @zero_neg).should == true + (@zero_neg <= @zero_pos).should == true + (@zero_pos <= @zero_neg).should == true + end - it "properly handles infinity values" do - @values.each { |val| - (val <= @infinity).should == true - (@infinity <= val).should == false - (val <= @infinity_neg).should == false - (@infinity_neg <= val).should == true - } - (@infinity <= @infinity).should == true - (@infinity_neg <= @infinity_neg).should == true - (@infinity <= @infinity_neg).should == false - (@infinity_neg <= @infinity).should == true - end + it "properly handles infinity values" do + @values.each { |val| + (val <= @infinity).should == true + (@infinity <= val).should == false + (val <= @infinity_neg).should == false + (@infinity_neg <= val).should == true + } + (@infinity <= @infinity).should == true + (@infinity_neg <= @infinity_neg).should == true + (@infinity <= @infinity_neg).should == false + (@infinity_neg <= @infinity).should == true + end - it "properly handles Float infinity values" do - @values.each { |val| - (val <= @float_infinity).should == true - (@float_infinity <= val).should == false - (val <= @float_infinity_neg).should == false - (@float_infinity_neg <= val).should == true - } - end + it "properly handles Float infinity values" do + @values.each { |val| + (val <= @float_infinity).should == true + (@float_infinity <= val).should == false + (val <= @float_infinity_neg).should == false + (@float_infinity_neg <= val).should == true + } + end - it "properly handles NaN values" do - @values += [@infinity, @infinity_neg, @nan] - @values.each { |val| - (@nan <= val).should == false - (val <= @nan).should == false - } - end + it "properly handles NaN values" do + @values += [@infinity, @infinity_neg, @nan] + @values.each { |val| + (@nan <= val).should == false + (val <= @nan).should == false + } + end - it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do - -> {@zero <= nil }.should raise_error(ArgumentError) - -> {@infinity <= nil }.should raise_error(ArgumentError) - -> {@infinity_neg <= nil }.should raise_error(ArgumentError) - -> {@mixed <= nil }.should raise_error(ArgumentError) - -> {@pos_int <= nil }.should raise_error(ArgumentError) - -> {@neg_frac <= nil }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if the argument can't be coerced into a BigDecimal" do + -> {@zero <= nil }.should raise_error(ArgumentError) + -> {@infinity <= nil }.should raise_error(ArgumentError) + -> {@infinity_neg <= nil }.should raise_error(ArgumentError) + -> {@mixed <= nil }.should raise_error(ArgumentError) + -> {@pos_int <= nil }.should raise_error(ArgumentError) + -> {@neg_frac <= nil }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/library/bigdecimal/minus_spec.rb b/spec/ruby/library/bigdecimal/minus_spec.rb index e65b9bcffc9cb9..bd3c19584b84b8 100644 --- a/spec/ruby/library/bigdecimal/minus_spec.rb +++ b/spec/ruby/library/bigdecimal/minus_spec.rb @@ -1,69 +1,66 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#-" do - describe "BigDecimal#-" do - - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @two = BigDecimal("2") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end - it "returns a - b" do - (@two - @one).should == @one - (@one - @two).should == @one_minus - (@one - @one_minus).should == @two - (@frac_2 - @frac_1).should == BigDecimal("-0.1E-99999") - (@two - @two).should == @zero - (@frac_1 - @frac_1).should == @zero - (BigDecimal('1.23456789') - BigDecimal('1.2')).should == BigDecimal("0.03456789") - end + it "returns a - b" do + (@two - @one).should == @one + (@one - @two).should == @one_minus + (@one - @one_minus).should == @two + (@frac_2 - @frac_1).should == BigDecimal("-0.1E-99999") + (@two - @two).should == @zero + (@frac_1 - @frac_1).should == @zero + (BigDecimal('1.23456789') - BigDecimal('1.2')).should == BigDecimal("0.03456789") + end - it "returns NaN if NaN is involved" do - (@one - @nan).should.nan? - (@nan - @one).should.nan? - (@nan - @nan).should.nan? - (@nan - @infinity).should.nan? - (@nan - @infinity_minus).should.nan? - (@infinity - @nan).should.nan? - (@infinity_minus - @nan).should.nan? - end + it "returns NaN if NaN is involved" do + (@one - @nan).should.nan? + (@nan - @one).should.nan? + (@nan - @nan).should.nan? + (@nan - @infinity).should.nan? + (@nan - @infinity_minus).should.nan? + (@infinity - @nan).should.nan? + (@infinity_minus - @nan).should.nan? + end - it "returns NaN both operands are infinite with the same sign" do - (@infinity - @infinity).should.nan? - (@infinity_minus - @infinity_minus).should.nan? - end + it "returns NaN both operands are infinite with the same sign" do + (@infinity - @infinity).should.nan? + (@infinity_minus - @infinity_minus).should.nan? + end - it "returns Infinity or -Infinity if these are involved" do - (@infinity - @infinity_minus).should == @infinity - (@infinity_minus - @infinity).should == @infinity_minus + it "returns Infinity or -Infinity if these are involved" do + (@infinity - @infinity_minus).should == @infinity + (@infinity_minus - @infinity).should == @infinity_minus - (@infinity - @zero).should == @infinity - (@infinity - @frac_2).should == @infinity - (@infinity - @two).should == @infinity - (@infinity - @one_minus).should == @infinity + (@infinity - @zero).should == @infinity + (@infinity - @frac_2).should == @infinity + (@infinity - @two).should == @infinity + (@infinity - @one_minus).should == @infinity - (@zero - @infinity).should == @infinity_minus - (@frac_2 - @infinity).should == @infinity_minus - (@two - @infinity).should == @infinity_minus - (@one_minus - @infinity).should == @infinity_minus - end + (@zero - @infinity).should == @infinity_minus + (@frac_2 - @infinity).should == @infinity_minus + (@two - @infinity).should == @infinity_minus + (@one_minus - @infinity).should == @infinity_minus + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) - (@one - object).should == BigDecimal("-41") - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) + (@one - object).should == BigDecimal("-41") end - end + end diff --git a/spec/ruby/library/bigdecimal/mode_spec.rb b/spec/ruby/library/bigdecimal/mode_spec.rb index 62aeb773573a8f..73fa1a118eaf41 100644 --- a/spec/ruby/library/bigdecimal/mode_spec.rb +++ b/spec/ruby/library/bigdecimal/mode_spec.rb @@ -1,39 +1,36 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal.mode" do - #the default value of BigDecimal exception constants is false - after :each do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) - BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) - BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) - end +describe "BigDecimal.mode" do + #the default value of BigDecimal exception constants is false + after :each do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) + BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) + end - it "returns the appropriate value and continue the computation if the flag is false" do - BigDecimal("NaN").add(BigDecimal("1"),0).should.nan? - BigDecimal("0").add(BigDecimal("Infinity"),0).should == BigDecimal("Infinity") - BigDecimal("1").quo(BigDecimal("0")).should == BigDecimal("Infinity") - end + it "returns the appropriate value and continue the computation if the flag is false" do + BigDecimal("NaN").add(BigDecimal("1"),0).should.nan? + BigDecimal("0").add(BigDecimal("Infinity"),0).should == BigDecimal("Infinity") + BigDecimal("1").quo(BigDecimal("0")).should == BigDecimal("Infinity") + end - it "returns Infinity when too big" do - BigDecimal("1E11111111111111111111").should == BigDecimal("Infinity") - (BigDecimal("1E1000000000000000000")**10).should == BigDecimal("Infinity") - end + it "returns Infinity when too big" do + BigDecimal("1E11111111111111111111").should == BigDecimal("Infinity") + (BigDecimal("1E1000000000000000000")**10).should == BigDecimal("Infinity") + end - it "raise an exception if the flag is true" do - BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) - -> { BigDecimal("NaN").add(BigDecimal("1"),0) }.should raise_error(FloatDomainError) - BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) - -> { BigDecimal("0").add(BigDecimal("Infinity"),0) }.should raise_error(FloatDomainError) - BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) - -> { BigDecimal("1").quo(BigDecimal("0")) }.should raise_error(FloatDomainError) - BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) - -> { BigDecimal("1E11111111111111111111") }.should raise_error(FloatDomainError) - -> { (BigDecimal("1E1000000000000000000")**10) }.should raise_error(FloatDomainError) - end + it "raise an exception if the flag is true" do + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) + -> { BigDecimal("NaN").add(BigDecimal("1"),0) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) + -> { BigDecimal("0").add(BigDecimal("Infinity"),0) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) + -> { BigDecimal("1").quo(BigDecimal("0")) }.should raise_error(FloatDomainError) + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) + -> { BigDecimal("1E11111111111111111111") }.should raise_error(FloatDomainError) + -> { (BigDecimal("1E1000000000000000000")**10) }.should raise_error(FloatDomainError) end end diff --git a/spec/ruby/library/bigdecimal/modulo_spec.rb b/spec/ruby/library/bigdecimal/modulo_spec.rb index 682b9ec334f6b4..035d31bd9828e3 100644 --- a/spec/ruby/library/bigdecimal/modulo_spec.rb +++ b/spec/ruby/library/bigdecimal/modulo_spec.rb @@ -1,15 +1,12 @@ require_relative '../../spec_helper' +require_relative 'shared/modulo' -ruby_version_is ""..."3.4" do - require_relative 'shared/modulo' - - describe "BigDecimal#%" do - it_behaves_like :bigdecimal_modulo, :% - it_behaves_like :bigdecimal_modulo_zerodivisionerror, :% - end +describe "BigDecimal#%" do + it_behaves_like :bigdecimal_modulo, :% + it_behaves_like :bigdecimal_modulo_zerodivisionerror, :% +end - describe "BigDecimal#modulo" do - it_behaves_like :bigdecimal_modulo, :modulo - it_behaves_like :bigdecimal_modulo_zerodivisionerror, :modulo - end +describe "BigDecimal#modulo" do + it_behaves_like :bigdecimal_modulo, :modulo + it_behaves_like :bigdecimal_modulo_zerodivisionerror, :modulo end diff --git a/spec/ruby/library/bigdecimal/mult_spec.rb b/spec/ruby/library/bigdecimal/mult_spec.rb index 29cb0c226d917b..b7f8044b0b2202 100644 --- a/spec/ruby/library/bigdecimal/mult_spec.rb +++ b/spec/ruby/library/bigdecimal/mult_spec.rb @@ -1,35 +1,32 @@ require_relative '../../spec_helper' +require_relative 'shared/mult' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/mult' - require 'bigdecimal' - - describe "BigDecimal#mult" do - it_behaves_like :bigdecimal_mult, :mult, [10] - end +describe "BigDecimal#mult" do + it_behaves_like :bigdecimal_mult, :mult, [10] +end - describe "BigDecimal#mult" do - before :each do - @one = BigDecimal "1" - @e3_minus = BigDecimal("3E-20001") - @e3_plus = BigDecimal("3E20001") - @e = BigDecimal "1.00000000000000000000123456789" - @tolerance = @e.sub @one, 1000 - @tolerance2 = BigDecimal "30001E-20005" +describe "BigDecimal#mult" do + before :each do + @one = BigDecimal "1" + @e3_minus = BigDecimal("3E-20001") + @e3_plus = BigDecimal("3E20001") + @e = BigDecimal "1.00000000000000000000123456789" + @tolerance = @e.sub @one, 1000 + @tolerance2 = BigDecimal "30001E-20005" - end + end - it "multiply self with other with (optional) precision" do - @e.mult(@one, 1).should be_close(@one, @tolerance) - @e3_minus.mult(@one, 1).should be_close(0, @tolerance2) - end + it "multiply self with other with (optional) precision" do + @e.mult(@one, 1).should be_close(@one, @tolerance) + @e3_minus.mult(@one, 1).should be_close(0, @tolerance2) + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) - @e3_minus.mult(object, 1).should == BigDecimal("9") - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) + @e3_minus.mult(object, 1).should == BigDecimal("9") end end end diff --git a/spec/ruby/library/bigdecimal/multiply_spec.rb b/spec/ruby/library/bigdecimal/multiply_spec.rb index 2887bcc5a928e0..a8ce1da32edc60 100644 --- a/spec/ruby/library/bigdecimal/multiply_spec.rb +++ b/spec/ruby/library/bigdecimal/multiply_spec.rb @@ -1,44 +1,41 @@ require_relative '../../spec_helper' +require_relative 'shared/mult' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/mult' - require 'bigdecimal' +describe "BigDecimal#*" do + it_behaves_like :bigdecimal_mult, :*, [] +end - describe "BigDecimal#*" do - it_behaves_like :bigdecimal_mult, :*, [] +describe "BigDecimal#*" do + before :each do + @three = BigDecimal("3") + @e3_minus = BigDecimal("3E-20001") + @e3_plus = BigDecimal("3E20001") + @e = BigDecimal("1.00000000000000000000123456789") + @one = BigDecimal("1") end - describe "BigDecimal#*" do - before :each do - @three = BigDecimal("3") - @e3_minus = BigDecimal("3E-20001") - @e3_plus = BigDecimal("3E20001") - @e = BigDecimal("1.00000000000000000000123456789") - @one = BigDecimal("1") - end - - it "multiply self with other" do - (@one * @one).should == @one - (@e3_minus * @e3_plus).should == BigDecimal("9") - # Can't do this till we implement ** - # (@e3_minus * @e3_minus).should == @e3_minus ** 2 - # So let's rewrite it as: - (@e3_minus * @e3_minus).should == BigDecimal("9E-40002") - (@e * @one).should == @e - end + it "multiply self with other" do + (@one * @one).should == @one + (@e3_minus * @e3_plus).should == BigDecimal("9") + # Can't do this till we implement ** + # (@e3_minus * @e3_minus).should == @e3_minus ** 2 + # So let's rewrite it as: + (@e3_minus * @e3_minus).should == BigDecimal("9E-40002") + (@e * @one).should == @e + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) - (@e3_minus * object).should == BigDecimal("9") - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) + (@e3_minus * object).should == BigDecimal("9") end + end - describe "with Rational" do - it "produces a BigDecimal" do - (@three * Rational(500, 2)).should == BigDecimal("0.75e3") - end + describe "with Rational" do + it "produces a BigDecimal" do + (@three * Rational(500, 2)).should == BigDecimal("0.75e3") end end end diff --git a/spec/ruby/library/bigdecimal/nan_spec.rb b/spec/ruby/library/bigdecimal/nan_spec.rb index 0839ed675df97b..9eaf69b6106c00 100644 --- a/spec/ruby/library/bigdecimal/nan_spec.rb +++ b/spec/ruby/library/bigdecimal/nan_spec.rb @@ -1,26 +1,23 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#nan?" do - describe "BigDecimal#nan?" do - - it "returns true if self is not a number" do - BigDecimal("NaN").should.nan? - end - - it "returns false if self is not a NaN" do - BigDecimal("Infinity").should_not.nan? - BigDecimal("-Infinity").should_not.nan? - BigDecimal("0").should_not.nan? - BigDecimal("+0").should_not.nan? - BigDecimal("-0").should_not.nan? - BigDecimal("2E40001").should_not.nan? - BigDecimal("3E-20001").should_not.nan? - BigDecimal("0E-200000000").should_not.nan? - BigDecimal("0E200000000000").should_not.nan? - BigDecimal("0.000000000000000000000000").should_not.nan? - end + it "returns true if self is not a number" do + BigDecimal("NaN").should.nan? + end + it "returns false if self is not a NaN" do + BigDecimal("Infinity").should_not.nan? + BigDecimal("-Infinity").should_not.nan? + BigDecimal("0").should_not.nan? + BigDecimal("+0").should_not.nan? + BigDecimal("-0").should_not.nan? + BigDecimal("2E40001").should_not.nan? + BigDecimal("3E-20001").should_not.nan? + BigDecimal("0E-200000000").should_not.nan? + BigDecimal("0E200000000000").should_not.nan? + BigDecimal("0.000000000000000000000000").should_not.nan? end + end diff --git a/spec/ruby/library/bigdecimal/nonzero_spec.rb b/spec/ruby/library/bigdecimal/nonzero_spec.rb index 895e8cc429f294..f43c4393cd3045 100644 --- a/spec/ruby/library/bigdecimal/nonzero_spec.rb +++ b/spec/ruby/library/bigdecimal/nonzero_spec.rb @@ -1,32 +1,29 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#nonzero?" do - describe "BigDecimal#nonzero?" do - - it "returns self if self doesn't equal zero" do - # documentation says, it returns true. (04/10/08) - e2_plus = BigDecimal("2E40001") - e3_minus = BigDecimal("3E-20001") - infinity = BigDecimal("Infinity") - infinity_minus = BigDecimal("-Infinity") - nan = BigDecimal("NaN") - infinity.nonzero?.should equal(infinity) - infinity_minus.nonzero?.should equal(infinity_minus) - nan.nonzero?.should equal(nan) - e3_minus.nonzero?.should equal(e3_minus) - e2_plus.nonzero?.should equal(e2_plus) - end - - it "returns nil otherwise" do - # documentation states, it should return false. (04/10/08) - really_small_zero = BigDecimal("0E-200000000") - really_big_zero = BigDecimal("0E200000000000") - really_small_zero.nonzero?.should == nil - really_big_zero.nonzero?.should == nil - BigDecimal("0.000000000000000000000000").nonzero?.should == nil - end + it "returns self if self doesn't equal zero" do + # documentation says, it returns true. (04/10/08) + e2_plus = BigDecimal("2E40001") + e3_minus = BigDecimal("3E-20001") + infinity = BigDecimal("Infinity") + infinity_minus = BigDecimal("-Infinity") + nan = BigDecimal("NaN") + infinity.nonzero?.should equal(infinity) + infinity_minus.nonzero?.should equal(infinity_minus) + nan.nonzero?.should equal(nan) + e3_minus.nonzero?.should equal(e3_minus) + e2_plus.nonzero?.should equal(e2_plus) + end + it "returns nil otherwise" do + # documentation states, it should return false. (04/10/08) + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + really_small_zero.nonzero?.should == nil + really_big_zero.nonzero?.should == nil + BigDecimal("0.000000000000000000000000").nonzero?.should == nil end + end diff --git a/spec/ruby/library/bigdecimal/plus_spec.rb b/spec/ruby/library/bigdecimal/plus_spec.rb index e1ae0e316382b8..d1934841c8ded7 100644 --- a/spec/ruby/library/bigdecimal/plus_spec.rb +++ b/spec/ruby/library/bigdecimal/plus_spec.rb @@ -1,57 +1,54 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#+" do + + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @ten = BigDecimal("10") + @eleven = BigDecimal("11") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#+" do - - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @ten = BigDecimal("10") - @eleven = BigDecimal("11") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end - - it "returns a + b" do - (@two + @one).should == @three - (@one + @two).should == @three - (@one + @one_minus).should == @zero - (@zero + @one).should == @one - (@ten + @one).should == @eleven - (@frac_1 + @frac_2).should == BigDecimal("1.9E-99999") - (@frac_2 + @frac_1).should == BigDecimal("1.9E-99999") - (@frac_1 + @frac_1).should == BigDecimal("2E-99999") - end + it "returns a + b" do + (@two + @one).should == @three + (@one + @two).should == @three + (@one + @one_minus).should == @zero + (@zero + @one).should == @one + (@ten + @one).should == @eleven + (@frac_1 + @frac_2).should == BigDecimal("1.9E-99999") + (@frac_2 + @frac_1).should == BigDecimal("1.9E-99999") + (@frac_1 + @frac_1).should == BigDecimal("2E-99999") + end - it "returns NaN if NaN is involved" do - (@one + @nan).should.nan? - (@nan + @one).should.nan? - end + it "returns NaN if NaN is involved" do + (@one + @nan).should.nan? + (@nan + @one).should.nan? + end - it "returns Infinity or -Infinity if these are involved" do - (@zero + @infinity).should == @infinity - (@frac_2 + @infinity).should == @infinity - (@two + @infinity_minus).should == @infinity_minus - end + it "returns Infinity or -Infinity if these are involved" do + (@zero + @infinity).should == @infinity + (@frac_2 + @infinity).should == @infinity + (@two + @infinity_minus).should == @infinity_minus + end - it "returns NaN if Infinity + (- Infinity)" do - (@infinity + @infinity_minus).should.nan? - end + it "returns NaN if Infinity + (- Infinity)" do + (@infinity + @infinity_minus).should.nan? + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) - (@one + object).should == BigDecimal("43") - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@one).and_return([@one, BigDecimal("42")]) + (@one + object).should == BigDecimal("43") end end end diff --git a/spec/ruby/library/bigdecimal/power_spec.rb b/spec/ruby/library/bigdecimal/power_spec.rb index 6ab0b9f2ca15f1..63a45a18872470 100644 --- a/spec/ruby/library/bigdecimal/power_spec.rb +++ b/spec/ruby/library/bigdecimal/power_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require_relative 'shared/power' -ruby_version_is ""..."3.4" do - require_relative 'shared/power' - - describe "BigDecimal#power" do - it_behaves_like :bigdecimal_power, :power - end +describe "BigDecimal#power" do + it_behaves_like :bigdecimal_power, :power end diff --git a/spec/ruby/library/bigdecimal/precs_spec.rb b/spec/ruby/library/bigdecimal/precs_spec.rb index d32778b5417af7..5fda8d30879a56 100644 --- a/spec/ruby/library/bigdecimal/precs_spec.rb +++ b/spec/ruby/library/bigdecimal/precs_spec.rb @@ -1,57 +1,54 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#precs" do + before :each do + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero = BigDecimal("0") + @zero_neg = BigDecimal("-0") - describe "BigDecimal#precs" do - before :each do - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero = BigDecimal("0") - @zero_neg = BigDecimal("-0") - - @arr = [BigDecimal("2E40001"), BigDecimal("3E-20001"),\ - @infinity, @infinity_neg, @nan, @zero, @zero_neg] - @precision = BigDecimal::BASE.to_s.length - 1 - end + @arr = [BigDecimal("2E40001"), BigDecimal("3E-20001"),\ + @infinity, @infinity_neg, @nan, @zero, @zero_neg] + @precision = BigDecimal::BASE.to_s.length - 1 + end - it "returns array of two values" do - suppress_warning do - @arr.each do |x| - x.precs.kind_of?(Array).should == true - x.precs.size.should == 2 - end + it "returns array of two values" do + suppress_warning do + @arr.each do |x| + x.precs.kind_of?(Array).should == true + x.precs.size.should == 2 end end + end - it "returns Integers as array values" do - suppress_warning do - @arr.each do |x| - x.precs[0].kind_of?(Integer).should == true - x.precs[1].kind_of?(Integer).should == true - end + it "returns Integers as array values" do + suppress_warning do + @arr.each do |x| + x.precs[0].kind_of?(Integer).should == true + x.precs[1].kind_of?(Integer).should == true end end + end - it "returns the current value of significant digits as the first value" do - suppress_warning do - BigDecimal("3.14159").precs[0].should >= 6 - BigDecimal('1').precs[0].should == BigDecimal('1' + '0' * 100).precs[0] - [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| - value.precs[0].should <= @precision - end + it "returns the current value of significant digits as the first value" do + suppress_warning do + BigDecimal("3.14159").precs[0].should >= 6 + BigDecimal('1').precs[0].should == BigDecimal('1' + '0' * 100).precs[0] + [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| + value.precs[0].should <= @precision end end + end - it "returns the maximum number of significant digits as the second value" do - suppress_warning do - BigDecimal("3.14159").precs[1].should >= 6 - BigDecimal('1').precs[1].should >= 1 - BigDecimal('1' + '0' * 100).precs[1].should >= 101 - [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| - value.precs[1].should >= 1 - end + it "returns the maximum number of significant digits as the second value" do + suppress_warning do + BigDecimal("3.14159").precs[1].should >= 6 + BigDecimal('1').precs[1].should >= 1 + BigDecimal('1' + '0' * 100).precs[1].should >= 101 + [@infinity, @infinity_neg, @nan, @zero, @zero_neg].each do |value| + value.precs[1].should >= 1 end end end diff --git a/spec/ruby/library/bigdecimal/quo_spec.rb b/spec/ruby/library/bigdecimal/quo_spec.rb index b839c380b480eb..65a4330303d5ef 100644 --- a/spec/ruby/library/bigdecimal/quo_spec.rb +++ b/spec/ruby/library/bigdecimal/quo_spec.rb @@ -1,15 +1,12 @@ require_relative '../../spec_helper' +require_relative 'shared/quo' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/quo' - require 'bigdecimal' +describe "BigDecimal#quo" do + it_behaves_like :bigdecimal_quo, :quo, [] - describe "BigDecimal#quo" do - it_behaves_like :bigdecimal_quo, :quo, [] - - it "returns NaN if NaN is involved" do - BigDecimal("1").quo(BigDecimal("NaN")).should.nan? - BigDecimal("NaN").quo(BigDecimal("1")).should.nan? - end + it "returns NaN if NaN is involved" do + BigDecimal("1").quo(BigDecimal("NaN")).should.nan? + BigDecimal("NaN").quo(BigDecimal("1")).should.nan? end end diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb index dde0a5bc2dda6f..bac5f37ba968c1 100644 --- a/spec/ruby/library/bigdecimal/remainder_spec.rb +++ b/spec/ruby/library/bigdecimal/remainder_spec.rb @@ -1,97 +1,94 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#remainder" do + + before :each do + @zero = BigDecimal("0") + @one = BigDecimal("1") + @three = BigDecimal("3") + @mixed = BigDecimal("1.23456789") + @pos_int = BigDecimal("2E5555") + @neg_int = BigDecimal("-2E5555") + @pos_frac = BigDecimal("2E-9999") + @neg_frac = BigDecimal("-2E-9999") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#remainder" do - - before :each do - @zero = BigDecimal("0") - @one = BigDecimal("1") - @three = BigDecimal("3") - @mixed = BigDecimal("1.23456789") - @pos_int = BigDecimal("2E5555") - @neg_int = BigDecimal("-2E5555") - @pos_frac = BigDecimal("2E-9999") - @neg_frac = BigDecimal("-2E-9999") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end - - it "it equals modulo, if both values are of same sign" do - BigDecimal('1234567890123456789012345679').remainder(BigDecimal('1')).should == @zero - BigDecimal('123456789').remainder(BigDecimal('333333333333333333333333333E-50')).should == BigDecimal('0.12233333333333333333345679E-24') - - @mixed.remainder(@pos_frac).should == @mixed % @pos_frac - @pos_int.remainder(@pos_frac).should == @pos_int % @pos_frac - @neg_frac.remainder(@neg_int).should == @neg_frac % @neg_int - @neg_int.remainder(@neg_frac).should == @neg_int % @neg_frac - end - - it "means self-arg*(self/arg).truncate" do - @mixed.remainder(@neg_frac).should == @mixed - @neg_frac * (@mixed / @neg_frac).truncate - @pos_int.remainder(@neg_frac).should == @pos_int - @neg_frac * (@pos_int / @neg_frac).truncate - @neg_frac.remainder(@pos_int).should == @neg_frac - @pos_int * (@neg_frac / @pos_int).truncate - @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate - end - - it "returns NaN used with zero" do - @mixed.remainder(@zero).should.nan? - @zero.remainder(@zero).should.nan? - end + it "it equals modulo, if both values are of same sign" do + BigDecimal('1234567890123456789012345679').remainder(BigDecimal('1')).should == @zero + BigDecimal('123456789').remainder(BigDecimal('333333333333333333333333333E-50')).should == BigDecimal('0.12233333333333333333345679E-24') - it "returns zero if used on zero" do - @zero.remainder(@mixed).should == @zero - end + @mixed.remainder(@pos_frac).should == @mixed % @pos_frac + @pos_int.remainder(@pos_frac).should == @pos_int % @pos_frac + @neg_frac.remainder(@neg_int).should == @neg_frac % @neg_int + @neg_int.remainder(@neg_frac).should == @neg_int % @neg_frac + end - it "returns NaN if NaN is involved" do - @nan.remainder(@nan).should.nan? - @nan.remainder(@one).should.nan? - @one.remainder(@nan).should.nan? - @infinity.remainder(@nan).should.nan? - @nan.remainder(@infinity).should.nan? - end + it "means self-arg*(self/arg).truncate" do + @mixed.remainder(@neg_frac).should == @mixed - @neg_frac * (@mixed / @neg_frac).truncate + @pos_int.remainder(@neg_frac).should == @pos_int - @neg_frac * (@pos_int / @neg_frac).truncate + @neg_frac.remainder(@pos_int).should == @neg_frac - @pos_int * (@neg_frac / @pos_int).truncate + @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate + end - version_is BigDecimal::VERSION, ""..."3.1.4" do #ruby_version_is ""..."3.3" do - it "returns NaN if Infinity is involved" do - @infinity.remainder(@infinity).should.nan? - @infinity.remainder(@one).should.nan? - @infinity.remainder(@mixed).should.nan? - @infinity.remainder(@one_minus).should.nan? - @infinity.remainder(@frac_1).should.nan? - @one.remainder(@infinity).should.nan? + it "returns NaN used with zero" do + @mixed.remainder(@zero).should.nan? + @zero.remainder(@zero).should.nan? + end - @infinity_minus.remainder(@infinity_minus).should.nan? - @infinity_minus.remainder(@one).should.nan? - @one.remainder(@infinity_minus).should.nan? - @frac_2.remainder(@infinity_minus).should.nan? + it "returns zero if used on zero" do + @zero.remainder(@mixed).should == @zero + end - @infinity.remainder(@infinity_minus).should.nan? - @infinity_minus.remainder(@infinity).should.nan? - end - end + it "returns NaN if NaN is involved" do + @nan.remainder(@nan).should.nan? + @nan.remainder(@one).should.nan? + @one.remainder(@nan).should.nan? + @infinity.remainder(@nan).should.nan? + @nan.remainder(@infinity).should.nan? + end - it "coerces arguments to BigDecimal if possible" do - @three.remainder(2).should == @one + version_is BigDecimal::VERSION, ""..."3.1.4" do #ruby_version_is ""..."3.3" do + it "returns NaN if Infinity is involved" do + @infinity.remainder(@infinity).should.nan? + @infinity.remainder(@one).should.nan? + @infinity.remainder(@mixed).should.nan? + @infinity.remainder(@one_minus).should.nan? + @infinity.remainder(@frac_1).should.nan? + @one.remainder(@infinity).should.nan? + + @infinity_minus.remainder(@infinity_minus).should.nan? + @infinity_minus.remainder(@one).should.nan? + @one.remainder(@infinity_minus).should.nan? + @frac_2.remainder(@infinity_minus).should.nan? + + @infinity.remainder(@infinity_minus).should.nan? + @infinity_minus.remainder(@infinity).should.nan? end + end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@three).and_return([@three, 2]) - @three.remainder(object).should == @one - end - end + it "coerces arguments to BigDecimal if possible" do + @three.remainder(2).should == @one + end - it "raises TypeError if the argument cannot be coerced to BigDecimal" do - -> { - @one.remainder('2') - }.should raise_error(TypeError) + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@three).and_return([@three, 2]) + @three.remainder(object).should == @one end + end + it "raises TypeError if the argument cannot be coerced to BigDecimal" do + -> { + @one.remainder('2') + }.should raise_error(TypeError) end + end diff --git a/spec/ruby/library/bigdecimal/round_spec.rb b/spec/ruby/library/bigdecimal/round_spec.rb index 2c966ab44457fb..fba52df65d3b51 100644 --- a/spec/ruby/library/bigdecimal/round_spec.rb +++ b/spec/ruby/library/bigdecimal/round_spec.rb @@ -1,245 +1,242 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#round" do + before :each do + @one = BigDecimal("1") + @two = BigDecimal("2") + @three = BigDecimal("3") + + @neg_one = BigDecimal("-1") + @neg_two = BigDecimal("-2") + @neg_three = BigDecimal("-3") + + @p1_50 = BigDecimal("1.50") + @p1_51 = BigDecimal("1.51") + @p1_49 = BigDecimal("1.49") + @n1_50 = BigDecimal("-1.50") + @n1_51 = BigDecimal("-1.51") + @n1_49 = BigDecimal("-1.49") + + @p2_50 = BigDecimal("2.50") + @p2_51 = BigDecimal("2.51") + @p2_49 = BigDecimal("2.49") + @n2_50 = BigDecimal("-2.50") + @n2_51 = BigDecimal("-2.51") + @n2_49 = BigDecimal("-2.49") + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#round" do - before :each do - @one = BigDecimal("1") - @two = BigDecimal("2") - @three = BigDecimal("3") - - @neg_one = BigDecimal("-1") - @neg_two = BigDecimal("-2") - @neg_three = BigDecimal("-3") - - @p1_50 = BigDecimal("1.50") - @p1_51 = BigDecimal("1.51") - @p1_49 = BigDecimal("1.49") - @n1_50 = BigDecimal("-1.50") - @n1_51 = BigDecimal("-1.51") - @n1_49 = BigDecimal("-1.49") - - @p2_50 = BigDecimal("2.50") - @p2_51 = BigDecimal("2.51") - @p2_49 = BigDecimal("2.49") - @n2_50 = BigDecimal("-2.50") - @n2_51 = BigDecimal("-2.51") - @n2_49 = BigDecimal("-2.49") - end - - after :each do - BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) - end + after :each do + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) + end - it "uses default rounding method unless given" do - @p1_50.round(0).should == @two - @p1_51.round(0).should == @two - @p1_49.round(0).should == @one - @n1_50.round(0).should == @neg_two - @n1_51.round(0).should == @neg_two - @n1_49.round(0).should == @neg_one - - @p2_50.round(0).should == @three - @p2_51.round(0).should == @three - @p2_49.round(0).should == @two - @n2_50.round(0).should == @neg_three - @n2_51.round(0).should == @neg_three - @n2_49.round(0).should == @neg_two - - BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN) - - @p1_50.round(0).should == @one - @p1_51.round(0).should == @one - @p1_49.round(0).should == @one - @n1_50.round(0).should == @neg_one - @n1_51.round(0).should == @neg_one - @n1_49.round(0).should == @neg_one - - @p2_50.round(0).should == @two - @p2_51.round(0).should == @two - @p2_49.round(0).should == @two - @n2_50.round(0).should == @neg_two - @n2_51.round(0).should == @neg_two - @n2_49.round(0).should == @neg_two - end + it "uses default rounding method unless given" do + @p1_50.round(0).should == @two + @p1_51.round(0).should == @two + @p1_49.round(0).should == @one + @n1_50.round(0).should == @neg_two + @n1_51.round(0).should == @neg_two + @n1_49.round(0).should == @neg_one + + @p2_50.round(0).should == @three + @p2_51.round(0).should == @three + @p2_49.round(0).should == @two + @n2_50.round(0).should == @neg_three + @n2_51.round(0).should == @neg_three + @n2_49.round(0).should == @neg_two + + BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN) + + @p1_50.round(0).should == @one + @p1_51.round(0).should == @one + @p1_49.round(0).should == @one + @n1_50.round(0).should == @neg_one + @n1_51.round(0).should == @neg_one + @n1_49.round(0).should == @neg_one + + @p2_50.round(0).should == @two + @p2_51.round(0).should == @two + @p2_49.round(0).should == @two + @n2_50.round(0).should == @neg_two + @n2_51.round(0).should == @neg_two + @n2_49.round(0).should == @neg_two + end - ["BigDecimal::ROUND_UP", ":up"].each do |way| - describe way do - it "rounds values away from zero" do - mode = eval(way) - - @p1_50.round(0, mode).should == @two - @p1_51.round(0, mode).should == @two - @p1_49.round(0, mode).should == @two - @n1_50.round(0, mode).should == @neg_two - @n1_51.round(0, mode).should == @neg_two - @n1_49.round(0, mode).should == @neg_two - - @p2_50.round(0, mode).should == @three - @p2_51.round(0, mode).should == @three - @p2_49.round(0, mode).should == @three - @n2_50.round(0, mode).should == @neg_three - @n2_51.round(0, mode).should == @neg_three - @n2_49.round(0, mode).should == @neg_three - end + ["BigDecimal::ROUND_UP", ":up"].each do |way| + describe way do + it "rounds values away from zero" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @two + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_two + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @three + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_three end end + end - ["BigDecimal::ROUND_DOWN", ":down", ":truncate"].each do |way| - describe way do - it "rounds values towards zero" do - mode = eval(way) - - @p1_50.round(0, mode).should == @one - @p1_51.round(0, mode).should == @one - @p1_49.round(0, mode).should == @one - @n1_50.round(0, mode).should == @neg_one - @n1_51.round(0, mode).should == @neg_one - @n1_49.round(0, mode).should == @neg_one - - @p2_50.round(0, mode).should == @two - @p2_51.round(0, mode).should == @two - @p2_49.round(0, mode).should == @two - @n2_50.round(0, mode).should == @neg_two - @n2_51.round(0, mode).should == @neg_two - @n2_49.round(0, mode).should == @neg_two - end + ["BigDecimal::ROUND_DOWN", ":down", ":truncate"].each do |way| + describe way do + it "rounds values towards zero" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @one + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_one + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @two + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_two + @n2_49.round(0, mode).should == @neg_two end end + end - ["BigDecimal::ROUND_HALF_UP", ":half_up", ":default"].each do |way| - describe way do - it "rounds values >= 5 up, otherwise down" do - mode = eval(way) - - @p1_50.round(0, mode).should == @two - @p1_51.round(0, mode).should == @two - @p1_49.round(0, mode).should == @one - @n1_50.round(0, mode).should == @neg_two - @n1_51.round(0, mode).should == @neg_two - @n1_49.round(0, mode).should == @neg_one - - @p2_50.round(0, mode).should == @three - @p2_51.round(0, mode).should == @three - @p2_49.round(0, mode).should == @two - @n2_50.round(0, mode).should == @neg_three - @n2_51.round(0, mode).should == @neg_three - @n2_49.round(0, mode).should == @neg_two - end + ["BigDecimal::ROUND_HALF_UP", ":half_up", ":default"].each do |way| + describe way do + it "rounds values >= 5 up, otherwise down" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two end end + end - ["BigDecimal::ROUND_HALF_DOWN", ":half_down"].each do |way| - describe way do - it "rounds values > 5 up, otherwise down" do - mode = eval(way) - - @p1_50.round(0, mode).should == @one - @p1_51.round(0, mode).should == @two - @p1_49.round(0, mode).should == @one - @n1_50.round(0, mode).should == @neg_one - @n1_51.round(0, mode).should == @neg_two - @n1_49.round(0, mode).should == @neg_one - - @p2_50.round(0, mode).should == @two - @p2_51.round(0, mode).should == @three - @p2_49.round(0, mode).should == @two - @n2_50.round(0, mode).should == @neg_two - @n2_51.round(0, mode).should == @neg_three - @n2_49.round(0, mode).should == @neg_two - end + ["BigDecimal::ROUND_HALF_DOWN", ":half_down"].each do |way| + describe way do + it "rounds values > 5 up, otherwise down" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two end end + end - ["BigDecimal::ROUND_CEILING", ":ceiling", ":ceil"].each do |way| - describe way do - it "rounds values towards +infinity" do - mode = eval(way) - - @p1_50.round(0, mode).should == @two - @p1_51.round(0, mode).should == @two - @p1_49.round(0, mode).should == @two - @n1_50.round(0, mode).should == @neg_one - @n1_51.round(0, mode).should == @neg_one - @n1_49.round(0, mode).should == @neg_one - - @p2_50.round(0, mode).should == @three - @p2_51.round(0, mode).should == @three - @p2_49.round(0, mode).should == @three - @n2_50.round(0, mode).should == @neg_two - @n2_51.round(0, mode).should == @neg_two - @n2_49.round(0, mode).should == @neg_two - end + ["BigDecimal::ROUND_CEILING", ":ceiling", ":ceil"].each do |way| + describe way do + it "rounds values towards +infinity" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @two + @n1_50.round(0, mode).should == @neg_one + @n1_51.round(0, mode).should == @neg_one + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @three + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @three + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_two + @n2_49.round(0, mode).should == @neg_two end end + end - ["BigDecimal::ROUND_FLOOR", ":floor"].each do |way| - describe way do - it "rounds values towards -infinity" do - mode = eval(way) - - @p1_50.round(0, mode).should == @one - @p1_51.round(0, mode).should == @one - @p1_49.round(0, mode).should == @one - @n1_50.round(0, mode).should == @neg_two - @n1_51.round(0, mode).should == @neg_two - @n1_49.round(0, mode).should == @neg_two - - @p2_50.round(0, mode).should == @two - @p2_51.round(0, mode).should == @two - @p2_49.round(0, mode).should == @two - @n2_50.round(0, mode).should == @neg_three - @n2_51.round(0, mode).should == @neg_three - @n2_49.round(0, mode).should == @neg_three - end + ["BigDecimal::ROUND_FLOOR", ":floor"].each do |way| + describe way do + it "rounds values towards -infinity" do + mode = eval(way) + + @p1_50.round(0, mode).should == @one + @p1_51.round(0, mode).should == @one + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_two + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @two + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_three + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_three end end + end - ["BigDecimal::ROUND_HALF_EVEN", ":half_even", ":banker"].each do |way| - describe way do - it "rounds values > 5 up, < 5 down and == 5 towards even neighbor" do - mode = eval(way) - - @p1_50.round(0, mode).should == @two - @p1_51.round(0, mode).should == @two - @p1_49.round(0, mode).should == @one - @n1_50.round(0, mode).should == @neg_two - @n1_51.round(0, mode).should == @neg_two - @n1_49.round(0, mode).should == @neg_one - - @p2_50.round(0, mode).should == @two - @p2_51.round(0, mode).should == @three - @p2_49.round(0, mode).should == @two - @n2_50.round(0, mode).should == @neg_two - @n2_51.round(0, mode).should == @neg_three - @n2_49.round(0, mode).should == @neg_two - end + ["BigDecimal::ROUND_HALF_EVEN", ":half_even", ":banker"].each do |way| + describe way do + it "rounds values > 5 up, < 5 down and == 5 towards even neighbor" do + mode = eval(way) + + @p1_50.round(0, mode).should == @two + @p1_51.round(0, mode).should == @two + @p1_49.round(0, mode).should == @one + @n1_50.round(0, mode).should == @neg_two + @n1_51.round(0, mode).should == @neg_two + @n1_49.round(0, mode).should == @neg_one + + @p2_50.round(0, mode).should == @two + @p2_51.round(0, mode).should == @three + @p2_49.round(0, mode).should == @two + @n2_50.round(0, mode).should == @neg_two + @n2_51.round(0, mode).should == @neg_three + @n2_49.round(0, mode).should == @neg_two end end + end - it 'raise exception, if self is special value' do - -> { BigDecimal('NaN').round }.should raise_error(FloatDomainError) - -> { BigDecimal('Infinity').round }.should raise_error(FloatDomainError) - -> { BigDecimal('-Infinity').round }.should raise_error(FloatDomainError) - end + it 'raise exception, if self is special value' do + -> { BigDecimal('NaN').round }.should raise_error(FloatDomainError) + -> { BigDecimal('Infinity').round }.should raise_error(FloatDomainError) + -> { BigDecimal('-Infinity').round }.should raise_error(FloatDomainError) + end - it 'do not raise exception, if self is special value and precision is given' do - -> { BigDecimal('NaN').round(2) }.should_not raise_error(FloatDomainError) - -> { BigDecimal('Infinity').round(2) }.should_not raise_error(FloatDomainError) - -> { BigDecimal('-Infinity').round(2) }.should_not raise_error(FloatDomainError) - end + it 'do not raise exception, if self is special value and precision is given' do + -> { BigDecimal('NaN').round(2) }.should_not raise_error(FloatDomainError) + -> { BigDecimal('Infinity').round(2) }.should_not raise_error(FloatDomainError) + -> { BigDecimal('-Infinity').round(2) }.should_not raise_error(FloatDomainError) + end - version_is BigDecimal::VERSION, ''...'3.1.3' do #ruby_version_is ''...'3.2' do - it 'raise for a non-existent round mode' do - -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode") - end + version_is BigDecimal::VERSION, ''...'3.1.3' do #ruby_version_is ''...'3.2' do + it 'raise for a non-existent round mode' do + -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode") end + end - version_is BigDecimal::VERSION, '3.1.3' do #ruby_version_is '3.2' do - it 'raise for a non-existent round mode' do - -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode (nonsense)") - end + version_is BigDecimal::VERSION, '3.1.3' do #ruby_version_is '3.2' do + it 'raise for a non-existent round mode' do + -> { @p1_50.round(0, :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode (nonsense)") end end end diff --git a/spec/ruby/library/bigdecimal/sign_spec.rb b/spec/ruby/library/bigdecimal/sign_spec.rb index c43ac05393e85a..ae2c28e9fd2e14 100644 --- a/spec/ruby/library/bigdecimal/sign_spec.rb +++ b/spec/ruby/library/bigdecimal/sign_spec.rb @@ -1,49 +1,46 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#sign" do + + it "defines several constants for signs" do + # are these really correct? + BigDecimal::SIGN_POSITIVE_INFINITE.should == 3 + BigDecimal::SIGN_NEGATIVE_INFINITE.should == -3 + BigDecimal::SIGN_POSITIVE_ZERO.should == 1 + BigDecimal::SIGN_NEGATIVE_ZERO.should == -1 + BigDecimal::SIGN_POSITIVE_FINITE.should == 2 + BigDecimal::SIGN_NEGATIVE_FINITE.should == -2 + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#sign" do - - it "defines several constants for signs" do - # are these really correct? - BigDecimal::SIGN_POSITIVE_INFINITE.should == 3 - BigDecimal::SIGN_NEGATIVE_INFINITE.should == -3 - BigDecimal::SIGN_POSITIVE_ZERO.should == 1 - BigDecimal::SIGN_NEGATIVE_ZERO.should == -1 - BigDecimal::SIGN_POSITIVE_FINITE.should == 2 - BigDecimal::SIGN_NEGATIVE_FINITE.should == -2 - end - - it "returns positive value if BigDecimal greater than 0" do - BigDecimal("1").sign.should == BigDecimal::SIGN_POSITIVE_FINITE - BigDecimal("1E-20000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE - BigDecimal("1E200000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE - BigDecimal("Infinity").sign.should == BigDecimal::SIGN_POSITIVE_INFINITE - end - - it "returns negative value if BigDecimal less than 0" do - BigDecimal("-1").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE - BigDecimal("-1E-9990000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE - BigDecimal("-1E20000000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE - BigDecimal("-Infinity").sign.should == BigDecimal::SIGN_NEGATIVE_INFINITE - end - - it "returns positive zero if BigDecimal equals positive zero" do - BigDecimal("0").sign.should == BigDecimal::SIGN_POSITIVE_ZERO - BigDecimal("0E-200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO - BigDecimal("0E200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO - end - - it "returns negative zero if BigDecimal equals negative zero" do - BigDecimal("-0").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO - BigDecimal("-0E-200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO - BigDecimal("-0E200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO - end - - it "returns BigDecimal::SIGN_NaN if BigDecimal is NaN" do - BigDecimal("NaN").sign.should == BigDecimal::SIGN_NaN - end + it "returns positive value if BigDecimal greater than 0" do + BigDecimal("1").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("1E-20000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("1E200000000").sign.should == BigDecimal::SIGN_POSITIVE_FINITE + BigDecimal("Infinity").sign.should == BigDecimal::SIGN_POSITIVE_INFINITE + end + it "returns negative value if BigDecimal less than 0" do + BigDecimal("-1").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-1E-9990000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-1E20000000").sign.should == BigDecimal::SIGN_NEGATIVE_FINITE + BigDecimal("-Infinity").sign.should == BigDecimal::SIGN_NEGATIVE_INFINITE end + + it "returns positive zero if BigDecimal equals positive zero" do + BigDecimal("0").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + BigDecimal("0E-200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + BigDecimal("0E200000000").sign.should == BigDecimal::SIGN_POSITIVE_ZERO + end + + it "returns negative zero if BigDecimal equals negative zero" do + BigDecimal("-0").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + BigDecimal("-0E-200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + BigDecimal("-0E200000000").sign.should == BigDecimal::SIGN_NEGATIVE_ZERO + end + + it "returns BigDecimal::SIGN_NaN if BigDecimal is NaN" do + BigDecimal("NaN").sign.should == BigDecimal::SIGN_NaN + end + end diff --git a/spec/ruby/library/bigdecimal/split_spec.rb b/spec/ruby/library/bigdecimal/split_spec.rb index a8752e3239124a..f9b4bab5f7455b 100644 --- a/spec/ruby/library/bigdecimal/split_spec.rb +++ b/spec/ruby/library/bigdecimal/split_spec.rb @@ -1,89 +1,86 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#split" do - describe "BigDecimal#split" do + before :each do + @arr = BigDecimal("0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split + @arr_neg = BigDecimal("-0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split + @digits = "922337203685477580810101333333333333333333333333333" + @arr_big = BigDecimal("00#{@digits}000").split + @arr_big_neg = BigDecimal("-00#{@digits}000").split + @huge = BigDecimal('100000000000000000000000000000000000000000001E90000000').split - before :each do - @arr = BigDecimal("0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split - @arr_neg = BigDecimal("-0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1").split - @digits = "922337203685477580810101333333333333333333333333333" - @arr_big = BigDecimal("00#{@digits}000").split - @arr_big_neg = BigDecimal("-00#{@digits}000").split - @huge = BigDecimal('100000000000000000000000000000000000000000001E90000000').split - - @infinity = BigDecimal("Infinity") - @infinity_neg = BigDecimal("-Infinity") - @nan = BigDecimal("NaN") - @zero = BigDecimal("0") - @zero_neg = BigDecimal("-0") - end - - it "splits BigDecimal in an array with four values" do - @arr.size.should == 4 - end + @infinity = BigDecimal("Infinity") + @infinity_neg = BigDecimal("-Infinity") + @nan = BigDecimal("NaN") + @zero = BigDecimal("0") + @zero_neg = BigDecimal("-0") + end - it "first value: 1 for numbers > 0" do - @arr[0].should == 1 - @arr_big[0].should == 1 - @zero.split[0].should == 1 - @huge[0].should == 1 - BigDecimal("+0").split[0].should == 1 - BigDecimal("1E400").split[0].should == 1 - @infinity.split[0].should == 1 - end + it "splits BigDecimal in an array with four values" do + @arr.size.should == 4 + end - it "first value: -1 for numbers < 0" do - @arr_neg[0].should == -1 - @arr_big_neg[0].should == -1 - @zero_neg.split[0].should == -1 - BigDecimal("-1E400").split[0].should == -1 - @infinity_neg.split[0].should == -1 - end + it "first value: 1 for numbers > 0" do + @arr[0].should == 1 + @arr_big[0].should == 1 + @zero.split[0].should == 1 + @huge[0].should == 1 + BigDecimal("+0").split[0].should == 1 + BigDecimal("1E400").split[0].should == 1 + @infinity.split[0].should == 1 + end - it "first value: 0 if BigDecimal is NaN" do - BigDecimal("NaN").split[0].should == 0 - end + it "first value: -1 for numbers < 0" do + @arr_neg[0].should == -1 + @arr_big_neg[0].should == -1 + @zero_neg.split[0].should == -1 + BigDecimal("-1E400").split[0].should == -1 + @infinity_neg.split[0].should == -1 + end - it "second value: a string with the significant digits" do - string = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" - @arr[1].should == string - @arr_big[1].should == @digits - @arr_big_neg[1].should == @digits - @huge[1].should == "100000000000000000000000000000000000000000001" - @infinity.split[1].should == @infinity.to_s - @nan.split[1].should == @nan.to_s - @infinity_neg.split[1].should == @infinity.to_s - @zero.split[1].should == "0" - BigDecimal("-0").split[1].should == "0" - end + it "first value: 0 if BigDecimal is NaN" do + BigDecimal("NaN").split[0].should == 0 + end - it "third value: the base (currently always ten)" do - @arr[2].should == 10 - @arr_neg[2].should == 10 - @arr_big[2].should == 10 - @arr_big_neg[2].should == 10 - @huge[2].should == 10 - @infinity.split[2].should == 10 - @nan.split[2].should == 10 - @infinity_neg.split[2].should == 10 - @zero.split[2].should == 10 - @zero_neg.split[2].should == 10 - end + it "second value: a string with the significant digits" do + string = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043" + @arr[1].should == string + @arr_big[1].should == @digits + @arr_big_neg[1].should == @digits + @huge[1].should == "100000000000000000000000000000000000000000001" + @infinity.split[1].should == @infinity.to_s + @nan.split[1].should == @nan.to_s + @infinity_neg.split[1].should == @infinity.to_s + @zero.split[1].should == "0" + BigDecimal("-0").split[1].should == "0" + end - it "fourth value: the exponent" do - @arr[3].should == 1 - @arr_neg[3].should == 1 - @arr_big[3].should == 54 - @arr_big_neg[3].should == 54 - @huge[3].should == 90000045 - @infinity.split[3].should == 0 - @nan.split[3].should == 0 - @infinity_neg.split[3].should == 0 - @zero.split[3].should == 0 - @zero_neg.split[3].should == 0 - end + it "third value: the base (currently always ten)" do + @arr[2].should == 10 + @arr_neg[2].should == 10 + @arr_big[2].should == 10 + @arr_big_neg[2].should == 10 + @huge[2].should == 10 + @infinity.split[2].should == 10 + @nan.split[2].should == 10 + @infinity_neg.split[2].should == 10 + @zero.split[2].should == 10 + @zero_neg.split[2].should == 10 + end + it "fourth value: the exponent" do + @arr[3].should == 1 + @arr_neg[3].should == 1 + @arr_big[3].should == 54 + @arr_big_neg[3].should == 54 + @huge[3].should == 90000045 + @infinity.split[3].should == 0 + @nan.split[3].should == 0 + @infinity_neg.split[3].should == 0 + @zero.split[3].should == 0 + @zero_neg.split[3].should == 0 end + end diff --git a/spec/ruby/library/bigdecimal/sqrt_spec.rb b/spec/ruby/library/bigdecimal/sqrt_spec.rb index daf95dbfe94c9f..d149003b9f5a96 100644 --- a/spec/ruby/library/bigdecimal/sqrt_spec.rb +++ b/spec/ruby/library/bigdecimal/sqrt_spec.rb @@ -1,115 +1,112 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require 'bigdecimal' + +describe "BigDecimal#sqrt" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2.0") + @three = BigDecimal("3.0") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + end + + it "returns square root of 2 with desired precision" do + string = "1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157" + (1..99).each { |idx| + @two.sqrt(idx).should be_close(BigDecimal(string), BigDecimal("1E-#{idx-1}")) + } + end + + it "returns square root of 3 with desired precision" do + sqrt_3 = "1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575" + (1..99).each { |idx| + @three.sqrt(idx).should be_close(BigDecimal(sqrt_3), BigDecimal("1E-#{idx-1}")) + } + end + + it "returns square root of 121 with desired precision" do + BigDecimal('121').sqrt(5).should be_close(11, 0.00001) + end + + it "returns square root of 0.9E-99999 with desired precision" do + @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i + end + + it "raises ArgumentError when no argument is given" do + -> { + @one.sqrt + }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if a negative number is given" do + -> { + @one.sqrt(-1) + }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if 2 arguments are given" do + -> { + @one.sqrt(1, 1) + }.should raise_error(ArgumentError) + end -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - require 'bigdecimal' - - describe "BigDecimal#sqrt" do - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @two = BigDecimal("2.0") - @three = BigDecimal("3.0") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - end - - it "returns square root of 2 with desired precision" do - string = "1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157" - (1..99).each { |idx| - @two.sqrt(idx).should be_close(BigDecimal(string), BigDecimal("1E-#{idx-1}")) - } - end - - it "returns square root of 3 with desired precision" do - sqrt_3 = "1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575" - (1..99).each { |idx| - @three.sqrt(idx).should be_close(BigDecimal(sqrt_3), BigDecimal("1E-#{idx-1}")) - } - end - - it "returns square root of 121 with desired precision" do - BigDecimal('121').sqrt(5).should be_close(11, 0.00001) - end - - it "returns square root of 0.9E-99999 with desired precision" do - @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i - end - - it "raises ArgumentError when no argument is given" do - -> { - @one.sqrt - }.should raise_error(ArgumentError) - end - - it "raises ArgumentError if a negative number is given" do - -> { - @one.sqrt(-1) - }.should raise_error(ArgumentError) - end - - it "raises ArgumentError if 2 arguments are given" do - -> { - @one.sqrt(1, 1) - }.should raise_error(ArgumentError) - end - - it "raises TypeError if nil is given" do - -> { - @one.sqrt(nil) - }.should raise_error(TypeError) - end - - it "raises TypeError if a string is given" do - -> { - @one.sqrt("stuff") - }.should raise_error(TypeError) - end - - it "raises TypeError if a plain Object is given" do - -> { - @one.sqrt(Object.new) - }.should raise_error(TypeError) - end - - it "returns 1 if precision is 0 or 1" do - @one.sqrt(1).should == 1 - @one.sqrt(0).should == 1 - end - - it "raises FloatDomainError on negative values" do - -> { - BigDecimal('-1').sqrt(10) - }.should raise_error(FloatDomainError) - end - - it "returns positive infinity for infinity" do - @infinity.sqrt(1).should == @infinity - end - - it "raises FloatDomainError for negative infinity" do - -> { - @infinity_minus.sqrt(1) - }.should raise_error(FloatDomainError) - end - - it "raises FloatDomainError for NaN" do - -> { - @nan.sqrt(1) - }.should raise_error(FloatDomainError) - end - - it "returns 0 for 0, +0.0 and -0.0" do - @zero.sqrt(1).should == 0 - @zero_pos.sqrt(1).should == 0 - @zero_neg.sqrt(1).should == 0 - end + it "raises TypeError if nil is given" do + -> { + @one.sqrt(nil) + }.should raise_error(TypeError) + end + it "raises TypeError if a string is given" do + -> { + @one.sqrt("stuff") + }.should raise_error(TypeError) end + + it "raises TypeError if a plain Object is given" do + -> { + @one.sqrt(Object.new) + }.should raise_error(TypeError) + end + + it "returns 1 if precision is 0 or 1" do + @one.sqrt(1).should == 1 + @one.sqrt(0).should == 1 + end + + it "raises FloatDomainError on negative values" do + -> { + BigDecimal('-1').sqrt(10) + }.should raise_error(FloatDomainError) + end + + it "returns positive infinity for infinity" do + @infinity.sqrt(1).should == @infinity + end + + it "raises FloatDomainError for negative infinity" do + -> { + @infinity_minus.sqrt(1) + }.should raise_error(FloatDomainError) + end + + it "raises FloatDomainError for NaN" do + -> { + @nan.sqrt(1) + }.should raise_error(FloatDomainError) + end + + it "returns 0 for 0, +0.0 and -0.0" do + @zero.sqrt(1).should == 0 + @zero_pos.sqrt(1).should == 0 + @zero_neg.sqrt(1).should == 0 + end + end diff --git a/spec/ruby/library/bigdecimal/sub_spec.rb b/spec/ruby/library/bigdecimal/sub_spec.rb index aa4e2a86689115..bddfec2186d6a4 100644 --- a/spec/ruby/library/bigdecimal/sub_spec.rb +++ b/spec/ruby/library/bigdecimal/sub_spec.rb @@ -1,73 +1,70 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#sub" do - describe "BigDecimal#sub" do - - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - @frac_3 = BigDecimal("12345E10") - @frac_4 = BigDecimal("98765E10") - end + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @frac_3 = BigDecimal("12345E10") + @frac_4 = BigDecimal("98765E10") + end - it "returns a - b with given precision" do - # documentation states, that precision is optional - # but implementation raises ArgumentError if not given. + it "returns a - b with given precision" do + # documentation states, that precision is optional + # but implementation raises ArgumentError if not given. - @two.sub(@one, 1).should == @one - @one.sub(@two, 1).should == @one_minus - @one.sub(@one_minus, 1).should == @two - @frac_2.sub(@frac_1, 1000000).should == BigDecimal("-0.1E-99999") - @frac_2.sub(@frac_1, 1).should == BigDecimal("-0.1E-99999") - # the above two examples puzzle me. - in_arow_one = BigDecimal("1.23456789") - in_arow_two = BigDecimal("1.2345678") - in_arow_one.sub(in_arow_two, 10).should == BigDecimal("0.9E-7") - @two.sub(@two,1).should == @zero - @frac_1.sub(@frac_1, 1000000).should == @zero - end - - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.sub(object, 1).should == BigDecimal("-0.9E15") - end - end + @two.sub(@one, 1).should == @one + @one.sub(@two, 1).should == @one_minus + @one.sub(@one_minus, 1).should == @two + @frac_2.sub(@frac_1, 1000000).should == BigDecimal("-0.1E-99999") + @frac_2.sub(@frac_1, 1).should == BigDecimal("-0.1E-99999") + # the above two examples puzzle me. + in_arow_one = BigDecimal("1.23456789") + in_arow_two = BigDecimal("1.2345678") + in_arow_one.sub(in_arow_two, 10).should == BigDecimal("0.9E-7") + @two.sub(@two,1).should == @zero + @frac_1.sub(@frac_1, 1000000).should == @zero + end - describe "with Rational" do - it "produces a BigDecimal" do - (@three - Rational(500, 2)).should == BigDecimal('-0.247e3') - end + describe "with Object" do + it "tries to coerce the other operand to self" do + object = mock("Object") + object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) + @frac_3.sub(object, 1).should == BigDecimal("-0.9E15") end + end - it "returns NaN if NaN is involved" do - @one.sub(@nan, 1).should.nan? - @nan.sub(@one, 1).should.nan? + describe "with Rational" do + it "produces a BigDecimal" do + (@three - Rational(500, 2)).should == BigDecimal('-0.247e3') end + end - it "returns NaN if both values are infinite with the same signs" do - @infinity.sub(@infinity, 1).should.nan? - @infinity_minus.sub(@infinity_minus, 1).should.nan? - end + it "returns NaN if NaN is involved" do + @one.sub(@nan, 1).should.nan? + @nan.sub(@one, 1).should.nan? + end - it "returns Infinity or -Infinity if these are involved" do - @infinity.sub(@infinity_minus, 1).should == @infinity - @infinity_minus.sub(@infinity, 1).should == @infinity_minus - @zero.sub(@infinity, 1).should == @infinity_minus - @frac_2.sub( @infinity, 1).should == @infinity_minus - @two.sub(@infinity, 1).should == @infinity_minus - end + it "returns NaN if both values are infinite with the same signs" do + @infinity.sub(@infinity, 1).should.nan? + @infinity_minus.sub(@infinity_minus, 1).should.nan? + end + it "returns Infinity or -Infinity if these are involved" do + @infinity.sub(@infinity_minus, 1).should == @infinity + @infinity_minus.sub(@infinity, 1).should == @infinity_minus + @zero.sub(@infinity, 1).should == @infinity_minus + @frac_2.sub( @infinity, 1).should == @infinity_minus + @two.sub(@infinity, 1).should == @infinity_minus end + end diff --git a/spec/ruby/library/bigdecimal/to_d_spec.rb b/spec/ruby/library/bigdecimal/to_d_spec.rb index ae2f11fe6df7e0..50aea99bf7ecbe 100644 --- a/spec/ruby/library/bigdecimal/to_d_spec.rb +++ b/spec/ruby/library/bigdecimal/to_d_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' +require 'bigdecimal' +require 'bigdecimal/util' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - require 'bigdecimal/util' - - describe "Float#to_d" do - it "returns appropriate BigDecimal zero for signed zero" do - -0.0.to_d.sign.should == -1 - 0.0.to_d.sign.should == 1 - end +describe "Float#to_d" do + it "returns appropriate BigDecimal zero for signed zero" do + -0.0.to_d.sign.should == -1 + 0.0.to_d.sign.should == 1 end end diff --git a/spec/ruby/library/bigdecimal/to_f_spec.rb b/spec/ruby/library/bigdecimal/to_f_spec.rb index c91e123dbfac18..84d4d49de23c15 100644 --- a/spec/ruby/library/bigdecimal/to_f_spec.rb +++ b/spec/ruby/library/bigdecimal/to_f_spec.rb @@ -1,57 +1,54 @@ require_relative '../../spec_helper' +require 'bigdecimal' + +describe "BigDecimal#to_f" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @two = BigDecimal("2") + @three = BigDecimal("3") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @vals = [@one, @zero, @two, @three, @frac_1, @frac_2] + @spec_vals = [@zero_pos, @zero_neg, @nan, @infinity, @infinity_minus] + end + + it "returns number of type float" do + BigDecimal("3.14159").to_f.should be_kind_of(Float) + @vals.each { |val| val.to_f.should be_kind_of(Float) } + @spec_vals.each { |val| val.to_f.should be_kind_of(Float) } + end + + it "rounds correctly to Float precision" do + bigdec = BigDecimal("3.141592653589793238462643383279502884197169399375") + bigdec.to_f.should be_close(3.14159265358979, TOLERANCE) + @one.to_f.should == 1.0 + @two.to_f.should == 2.0 + @three.to_f.should be_close(3.0, TOLERANCE) + @one_minus.to_f.should == -1.0 + + # regression test for [ruby-talk:338957] + BigDecimal("10.03").to_f.should == 10.03 + end + + it "properly handles special values" do + @zero.to_f.should == 0 + @zero.to_f.to_s.should == "0.0" + + @nan.to_f.should.nan? + + @infinity.to_f.infinite?.should == 1 + @infinity_minus.to_f.infinite?.should == -1 + end -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#to_f" do - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @two = BigDecimal("2") - @three = BigDecimal("3") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - @vals = [@one, @zero, @two, @three, @frac_1, @frac_2] - @spec_vals = [@zero_pos, @zero_neg, @nan, @infinity, @infinity_minus] - end - - it "returns number of type float" do - BigDecimal("3.14159").to_f.should be_kind_of(Float) - @vals.each { |val| val.to_f.should be_kind_of(Float) } - @spec_vals.each { |val| val.to_f.should be_kind_of(Float) } - end - - it "rounds correctly to Float precision" do - bigdec = BigDecimal("3.141592653589793238462643383279502884197169399375") - bigdec.to_f.should be_close(3.14159265358979, TOLERANCE) - @one.to_f.should == 1.0 - @two.to_f.should == 2.0 - @three.to_f.should be_close(3.0, TOLERANCE) - @one_minus.to_f.should == -1.0 - - # regression test for [ruby-talk:338957] - BigDecimal("10.03").to_f.should == 10.03 - end - - it "properly handles special values" do - @zero.to_f.should == 0 - @zero.to_f.to_s.should == "0.0" - - @nan.to_f.should.nan? - - @infinity.to_f.infinite?.should == 1 - @infinity_minus.to_f.infinite?.should == -1 - end - - it "remembers negative zero when converted to float" do - @zero_neg.to_f.should == 0 - @zero_neg.to_f.to_s.should == "-0.0" - end + it "remembers negative zero when converted to float" do + @zero_neg.to_f.should == 0 + @zero_neg.to_f.to_s.should == "-0.0" end end diff --git a/spec/ruby/library/bigdecimal/to_i_spec.rb b/spec/ruby/library/bigdecimal/to_i_spec.rb index 08367374e2db8f..09481fce154915 100644 --- a/spec/ruby/library/bigdecimal/to_i_spec.rb +++ b/spec/ruby/library/bigdecimal/to_i_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require_relative 'shared/to_int' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/to_int' - require 'bigdecimal' - - describe "BigDecimal#to_i" do - it_behaves_like :bigdecimal_to_int, :to_i - end +describe "BigDecimal#to_i" do + it_behaves_like :bigdecimal_to_int, :to_i end diff --git a/spec/ruby/library/bigdecimal/to_int_spec.rb b/spec/ruby/library/bigdecimal/to_int_spec.rb index 8ded7bcaf970bd..4df674984525ff 100644 --- a/spec/ruby/library/bigdecimal/to_int_spec.rb +++ b/spec/ruby/library/bigdecimal/to_int_spec.rb @@ -1,11 +1,8 @@ require_relative '../../spec_helper' +require_relative 'shared/to_int' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require_relative 'shared/to_int' - require 'bigdecimal' - - describe "BigDecimal#to_int" do - it_behaves_like :bigdecimal_to_int, :to_int - end +describe "BigDecimal#to_int" do + it_behaves_like :bigdecimal_to_int, :to_int end diff --git a/spec/ruby/library/bigdecimal/to_r_spec.rb b/spec/ruby/library/bigdecimal/to_r_spec.rb index 0d787a2effdf8f..c350beff08c765 100644 --- a/spec/ruby/library/bigdecimal/to_r_spec.rb +++ b/spec/ruby/library/bigdecimal/to_r_spec.rb @@ -1,31 +1,28 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#to_r" do - describe "BigDecimal#to_r" do - - it "returns a Rational" do - BigDecimal("3.14159").to_r.should be_kind_of(Rational) - end - - it "returns a Rational with bignum values" do - r = BigDecimal("3.141592653589793238462643").to_r - r.numerator.should eql(3141592653589793238462643) - r.denominator.should eql(1000000000000000000000000) - end + it "returns a Rational" do + BigDecimal("3.14159").to_r.should be_kind_of(Rational) + end - it "returns a Rational from a BigDecimal with an exponent" do - r = BigDecimal("1E2").to_r - r.numerator.should eql(100) - r.denominator.should eql(1) - end + it "returns a Rational with bignum values" do + r = BigDecimal("3.141592653589793238462643").to_r + r.numerator.should eql(3141592653589793238462643) + r.denominator.should eql(1000000000000000000000000) + end - it "returns a Rational from a negative BigDecimal with an exponent" do - r = BigDecimal("-1E2").to_r - r.numerator.should eql(-100) - r.denominator.should eql(1) - end + it "returns a Rational from a BigDecimal with an exponent" do + r = BigDecimal("1E2").to_r + r.numerator.should eql(100) + r.denominator.should eql(1) + end + it "returns a Rational from a negative BigDecimal with an exponent" do + r = BigDecimal("-1E2").to_r + r.numerator.should eql(-100) + r.denominator.should eql(1) end + end diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb index 02f1ce0d3e1d67..ba9f960eb32450 100644 --- a/spec/ruby/library/bigdecimal/to_s_spec.rb +++ b/spec/ruby/library/bigdecimal/to_s_spec.rb @@ -1,103 +1,100 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#to_s" do - describe "BigDecimal#to_s" do - - before :each do - @bigdec_str = "3.14159265358979323846264338327950288419716939937" - @bigneg_str = "-3.1415926535897932384626433832795028841971693993" - @bigdec = BigDecimal(@bigdec_str) - @bigneg = BigDecimal(@bigneg_str) - @internal = Encoding.default_internal - end + before :each do + @bigdec_str = "3.14159265358979323846264338327950288419716939937" + @bigneg_str = "-3.1415926535897932384626433832795028841971693993" + @bigdec = BigDecimal(@bigdec_str) + @bigneg = BigDecimal(@bigneg_str) + @internal = Encoding.default_internal + end - after :each do - Encoding.default_internal = @internal - end + after :each do + Encoding.default_internal = @internal + end - it "return type is of class String" do - @bigdec.to_s.kind_of?(String).should == true - @bigneg.to_s.kind_of?(String).should == true - end + it "return type is of class String" do + @bigdec.to_s.kind_of?(String).should == true + @bigneg.to_s.kind_of?(String).should == true + end - it "the default format looks like 0.xxxxenn" do - @bigdec.to_s.should =~ /^0\.[0-9]*e[0-9]*$/ - end + it "the default format looks like 0.xxxxenn" do + @bigdec.to_s.should =~ /^0\.[0-9]*e[0-9]*$/ + end - it "does not add an exponent for zero values" do - BigDecimal("0").to_s.should == "0.0" - BigDecimal("+0").to_s.should == "0.0" - BigDecimal("-0").to_s.should == "-0.0" - end + it "does not add an exponent for zero values" do + BigDecimal("0").to_s.should == "0.0" + BigDecimal("+0").to_s.should == "0.0" + BigDecimal("-0").to_s.should == "-0.0" + end - it "takes an optional argument" do - -> {@bigdec.to_s("F")}.should_not raise_error() - end + it "takes an optional argument" do + -> {@bigdec.to_s("F")}.should_not raise_error() + end - it "starts with + if + is supplied and value is positive" do - @bigdec.to_s("+").should =~ /^\+.*/ - @bigneg.to_s("+").should_not =~ /^\+.*/ - end + it "starts with + if + is supplied and value is positive" do + @bigdec.to_s("+").should =~ /^\+.*/ + @bigneg.to_s("+").should_not =~ /^\+.*/ + end - it "inserts a space every n chars to fraction part, if integer n is supplied" do - re =\ - /\A0\.314 159 265 358 979 323 846 264 338 327 950 288 419 716 939 937E1\z/i - @bigdec.to_s(3).should =~ re - - str1 = '-123.45678 90123 45678 9' - BigDecimal("-123.45678901234567890").to_s('5F').should == str1 - # trailing zeroes removed - BigDecimal("1.00000000000").to_s('1F').should == "1.0" - # 0 is treated as no spaces - BigDecimal("1.2345").to_s('0F').should == "1.2345" - end + it "inserts a space every n chars to fraction part, if integer n is supplied" do + re =\ + /\A0\.314 159 265 358 979 323 846 264 338 327 950 288 419 716 939 937E1\z/i + @bigdec.to_s(3).should =~ re + + str1 = '-123.45678 90123 45678 9' + BigDecimal("-123.45678901234567890").to_s('5F').should == str1 + # trailing zeroes removed + BigDecimal("1.00000000000").to_s('1F').should == "1.0" + # 0 is treated as no spaces + BigDecimal("1.2345").to_s('0F').should == "1.2345" + end - version_is BigDecimal::VERSION, "3.1.5" do #ruby_version_is '3.3' do - it "inserts a space every n chars to integer part, if integer n is supplied" do - BigDecimal('1000010').to_s('5F').should == "10 00010.0" - end + version_is BigDecimal::VERSION, "3.1.5" do #ruby_version_is '3.3' do + it "inserts a space every n chars to integer part, if integer n is supplied" do + BigDecimal('1000010').to_s('5F').should == "10 00010.0" end + end - it "can return a leading space for values > 0" do - @bigdec.to_s(" F").should =~ /\ .*/ - @bigneg.to_s(" F").should_not =~ /\ .*/ - end + it "can return a leading space for values > 0" do + @bigdec.to_s(" F").should =~ /\ .*/ + @bigneg.to_s(" F").should_not =~ /\ .*/ + end - it "removes trailing spaces in floating point notation" do - BigDecimal('-123.45678901234567890').to_s('F').should == "-123.4567890123456789" - BigDecimal('1.2500').to_s('F').should == "1.25" - BigDecimal('0000.00000').to_s('F').should == "0.0" - BigDecimal('-00.000010000').to_s('F').should == "-0.00001" - BigDecimal("5.00000E-2").to_s("F").should == "0.05" + it "removes trailing spaces in floating point notation" do + BigDecimal('-123.45678901234567890').to_s('F').should == "-123.4567890123456789" + BigDecimal('1.2500').to_s('F').should == "1.25" + BigDecimal('0000.00000').to_s('F').should == "0.0" + BigDecimal('-00.000010000').to_s('F').should == "-0.00001" + BigDecimal("5.00000E-2").to_s("F").should == "0.05" - BigDecimal("500000").to_s("F").should == "500000.0" - BigDecimal("5E2").to_s("F").should == "500.0" - BigDecimal("-5E100").to_s("F").should == "-5" + "0" * 100 + ".0" - end + BigDecimal("500000").to_s("F").should == "500000.0" + BigDecimal("5E2").to_s("F").should == "500.0" + BigDecimal("-5E100").to_s("F").should == "-5" + "0" * 100 + ".0" + end - it "can use engineering notation" do - @bigdec.to_s("E").should =~ /^0\.[0-9]*E[0-9]*$/i - end + it "can use engineering notation" do + @bigdec.to_s("E").should =~ /^0\.[0-9]*E[0-9]*$/i + end - it "can use conventional floating point notation" do - %w[f F].each do |format_char| - @bigdec.to_s(format_char).should == @bigdec_str - @bigneg.to_s(format_char).should == @bigneg_str - str2 = "+123.45678901 23456789" - BigDecimal('123.45678901234567890').to_s("+8#{format_char}").should == str2 - end + it "can use conventional floating point notation" do + %w[f F].each do |format_char| + @bigdec.to_s(format_char).should == @bigdec_str + @bigneg.to_s(format_char).should == @bigneg_str + str2 = "+123.45678901 23456789" + BigDecimal('123.45678901234567890').to_s("+8#{format_char}").should == str2 end + end - it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do - Encoding.default_internal = nil - BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) - end + it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do + Encoding.default_internal = nil + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) + end - it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do - Encoding.default_internal = Encoding::IBM437 - BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) - end + it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do + Encoding.default_internal = Encoding::IBM437 + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) end end diff --git a/spec/ruby/library/bigdecimal/truncate_spec.rb b/spec/ruby/library/bigdecimal/truncate_spec.rb index fbb5b69779413d..4ad9eb92d1e760 100644 --- a/spec/ruby/library/bigdecimal/truncate_spec.rb +++ b/spec/ruby/library/bigdecimal/truncate_spec.rb @@ -1,84 +1,81 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#truncate" do - describe "BigDecimal#truncate" do - - before :each do - @arr = ['3.14159', '8.7', "0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1"] - @big = BigDecimal("123456.789") - @nan = BigDecimal('NaN') - @infinity = BigDecimal('Infinity') - @infinity_negative = BigDecimal('-Infinity') - end + before :each do + @arr = ['3.14159', '8.7', "0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593014782083152134043E1"] + @big = BigDecimal("123456.789") + @nan = BigDecimal('NaN') + @infinity = BigDecimal('Infinity') + @infinity_negative = BigDecimal('-Infinity') + end - it "returns value of type Integer." do - @arr.each do |x| - BigDecimal(x).truncate.kind_of?(Integer).should == true - end + it "returns value of type Integer." do + @arr.each do |x| + BigDecimal(x).truncate.kind_of?(Integer).should == true end + end - it "returns the integer part as a BigDecimal if no precision given" do - BigDecimal(@arr[0]).truncate.should == 3 - BigDecimal(@arr[1]).truncate.should == 8 - BigDecimal(@arr[2]).truncate.should == 3 - BigDecimal('0').truncate.should == 0 - BigDecimal('0.1').truncate.should == 0 - BigDecimal('-0.1').truncate.should == 0 - BigDecimal('1.5').truncate.should == 1 - BigDecimal('-1.5').truncate.should == -1 - BigDecimal('1E10').truncate.should == BigDecimal('1E10') - BigDecimal('-1E10').truncate.should == BigDecimal('-1E10') - BigDecimal('1.8888E10').truncate.should == BigDecimal('1.8888E10') - BigDecimal('-1E-1').truncate.should == 0 - end + it "returns the integer part as a BigDecimal if no precision given" do + BigDecimal(@arr[0]).truncate.should == 3 + BigDecimal(@arr[1]).truncate.should == 8 + BigDecimal(@arr[2]).truncate.should == 3 + BigDecimal('0').truncate.should == 0 + BigDecimal('0.1').truncate.should == 0 + BigDecimal('-0.1').truncate.should == 0 + BigDecimal('1.5').truncate.should == 1 + BigDecimal('-1.5').truncate.should == -1 + BigDecimal('1E10').truncate.should == BigDecimal('1E10') + BigDecimal('-1E10').truncate.should == BigDecimal('-1E10') + BigDecimal('1.8888E10').truncate.should == BigDecimal('1.8888E10') + BigDecimal('-1E-1').truncate.should == 0 + end - it "returns value of given precision otherwise" do - BigDecimal('-1.55').truncate(1).should == BigDecimal('-1.5') - BigDecimal('1.55').truncate(1).should == BigDecimal('1.5') - BigDecimal(@arr[0]).truncate(2).should == BigDecimal("3.14") - BigDecimal('123.456').truncate(2).should == BigDecimal("123.45") - BigDecimal('123.456789').truncate(4).should == BigDecimal("123.4567") - BigDecimal('0.456789').truncate(10).should == BigDecimal("0.456789") - BigDecimal('-1E-1').truncate(1).should == BigDecimal('-0.1') - BigDecimal('-1E-1').truncate(2).should == BigDecimal('-0.1E0') - BigDecimal('-1E-1').truncate.should == BigDecimal('0') - BigDecimal('-1E-1').truncate(0).should == BigDecimal('0') - BigDecimal('-1E-1').truncate(-1).should == BigDecimal('0') - BigDecimal('-1E-1').truncate(-2).should == BigDecimal('0') + it "returns value of given precision otherwise" do + BigDecimal('-1.55').truncate(1).should == BigDecimal('-1.5') + BigDecimal('1.55').truncate(1).should == BigDecimal('1.5') + BigDecimal(@arr[0]).truncate(2).should == BigDecimal("3.14") + BigDecimal('123.456').truncate(2).should == BigDecimal("123.45") + BigDecimal('123.456789').truncate(4).should == BigDecimal("123.4567") + BigDecimal('0.456789').truncate(10).should == BigDecimal("0.456789") + BigDecimal('-1E-1').truncate(1).should == BigDecimal('-0.1') + BigDecimal('-1E-1').truncate(2).should == BigDecimal('-0.1E0') + BigDecimal('-1E-1').truncate.should == BigDecimal('0') + BigDecimal('-1E-1').truncate(0).should == BigDecimal('0') + BigDecimal('-1E-1').truncate(-1).should == BigDecimal('0') + BigDecimal('-1E-1').truncate(-2).should == BigDecimal('0') - BigDecimal(@arr[1]).truncate(1).should == BigDecimal("8.7") - BigDecimal(@arr[2]).truncate(100).should == BigDecimal(\ - "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679") - end + BigDecimal(@arr[1]).truncate(1).should == BigDecimal("8.7") + BigDecimal(@arr[2]).truncate(100).should == BigDecimal(\ + "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679") + end - it "sets n digits left of the decimal point to 0, if given n < 0" do - @big.truncate(-1).should == BigDecimal("123450.0") - @big.truncate(-2).should == BigDecimal("123400.0") - BigDecimal(@arr[2]).truncate(-1).should == 0 - end + it "sets n digits left of the decimal point to 0, if given n < 0" do + @big.truncate(-1).should == BigDecimal("123450.0") + @big.truncate(-2).should == BigDecimal("123400.0") + BigDecimal(@arr[2]).truncate(-1).should == 0 + end - it "returns NaN if self is NaN" do - @nan.truncate(-1).should.nan? - @nan.truncate(+1).should.nan? - @nan.truncate(0).should.nan? - end + it "returns NaN if self is NaN" do + @nan.truncate(-1).should.nan? + @nan.truncate(+1).should.nan? + @nan.truncate(0).should.nan? + end - it "returns Infinity if self is infinite" do - @infinity.truncate(-1).should == @infinity - @infinity.truncate(+1).should == @infinity - @infinity.truncate(0).should == @infinity + it "returns Infinity if self is infinite" do + @infinity.truncate(-1).should == @infinity + @infinity.truncate(+1).should == @infinity + @infinity.truncate(0).should == @infinity - @infinity_negative.truncate(-1).should == @infinity_negative - @infinity_negative.truncate(+1).should == @infinity_negative - @infinity_negative.truncate(0).should == @infinity_negative - end + @infinity_negative.truncate(-1).should == @infinity_negative + @infinity_negative.truncate(+1).should == @infinity_negative + @infinity_negative.truncate(0).should == @infinity_negative + end - it "returns the same value if self is special value" do - -> { @nan.truncate }.should raise_error(FloatDomainError) - -> { @infinity.truncate }.should raise_error(FloatDomainError) - -> { @infinity_negative.truncate }.should raise_error(FloatDomainError) - end + it "returns the same value if self is special value" do + -> { @nan.truncate }.should raise_error(FloatDomainError) + -> { @infinity.truncate }.should raise_error(FloatDomainError) + -> { @infinity_negative.truncate }.should raise_error(FloatDomainError) end end diff --git a/spec/ruby/library/bigdecimal/uminus_spec.rb b/spec/ruby/library/bigdecimal/uminus_spec.rb index 612321915f325d..c780cdfac5acae 100644 --- a/spec/ruby/library/bigdecimal/uminus_spec.rb +++ b/spec/ruby/library/bigdecimal/uminus_spec.rb @@ -1,61 +1,58 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#-@" do - before :each do - @one = BigDecimal("1") - @zero = BigDecimal("0") - @zero_pos = BigDecimal("+0") - @zero_neg = BigDecimal("-0") - @nan = BigDecimal("NaN") - @infinity = BigDecimal("Infinity") - @infinity_minus = BigDecimal("-Infinity") - @one_minus = BigDecimal("-1") - @frac_1 = BigDecimal("1E-99999") - @frac_2 = BigDecimal("0.9E-99999") - @big = BigDecimal("333E99999") - @big_neg = BigDecimal("-333E99999") - @values = [@one, @zero, @zero_pos, @zero_neg, @infinity, - @infinity_minus, @one_minus, @frac_1, @frac_2, @big, @big_neg] - end +describe "BigDecimal#-@" do + before :each do + @one = BigDecimal("1") + @zero = BigDecimal("0") + @zero_pos = BigDecimal("+0") + @zero_neg = BigDecimal("-0") + @nan = BigDecimal("NaN") + @infinity = BigDecimal("Infinity") + @infinity_minus = BigDecimal("-Infinity") + @one_minus = BigDecimal("-1") + @frac_1 = BigDecimal("1E-99999") + @frac_2 = BigDecimal("0.9E-99999") + @big = BigDecimal("333E99999") + @big_neg = BigDecimal("-333E99999") + @values = [@one, @zero, @zero_pos, @zero_neg, @infinity, + @infinity_minus, @one_minus, @frac_1, @frac_2, @big, @big_neg] + end - it "negates self" do - @one.send(:-@).should == @one_minus - @one_minus.send(:-@).should == @one - @frac_1.send(:-@).should == BigDecimal("-1E-99999") - @frac_2.send(:-@).should == BigDecimal("-0.9E-99999") - @big.send(:-@).should == @big_neg - @big_neg.send(:-@).should == @big - BigDecimal("2.221").send(:-@).should == BigDecimal("-2.221") - BigDecimal("2E10000").send(:-@).should == BigDecimal("-2E10000") - some_number = BigDecimal("2455999221.5512") - some_number_neg = BigDecimal("-2455999221.5512") - some_number.send(:-@).should == some_number_neg - (-BigDecimal("-5.5")).should == BigDecimal("5.5") - another_number = BigDecimal("-8.551551551551551551") - another_number_pos = BigDecimal("8.551551551551551551") - another_number.send(:-@).should == another_number_pos - @values.each do |val| - (val.send(:-@).send(:-@)).should == val - end + it "negates self" do + @one.send(:-@).should == @one_minus + @one_minus.send(:-@).should == @one + @frac_1.send(:-@).should == BigDecimal("-1E-99999") + @frac_2.send(:-@).should == BigDecimal("-0.9E-99999") + @big.send(:-@).should == @big_neg + @big_neg.send(:-@).should == @big + BigDecimal("2.221").send(:-@).should == BigDecimal("-2.221") + BigDecimal("2E10000").send(:-@).should == BigDecimal("-2E10000") + some_number = BigDecimal("2455999221.5512") + some_number_neg = BigDecimal("-2455999221.5512") + some_number.send(:-@).should == some_number_neg + (-BigDecimal("-5.5")).should == BigDecimal("5.5") + another_number = BigDecimal("-8.551551551551551551") + another_number_pos = BigDecimal("8.551551551551551551") + another_number.send(:-@).should == another_number_pos + @values.each do |val| + (val.send(:-@).send(:-@)).should == val end + end - it "properly handles special values" do - @infinity.send(:-@).should == @infinity_minus - @infinity_minus.send(:-@).should == @infinity - @infinity.send(:-@).infinite?.should == -1 - @infinity_minus.send(:-@).infinite?.should == 1 + it "properly handles special values" do + @infinity.send(:-@).should == @infinity_minus + @infinity_minus.send(:-@).should == @infinity + @infinity.send(:-@).infinite?.should == -1 + @infinity_minus.send(:-@).infinite?.should == 1 - @zero.send(:-@).should == @zero - @zero.send(:-@).sign.should == -1 - @zero_pos.send(:-@).should == @zero - @zero_pos.send(:-@).sign.should == -1 - @zero_neg.send(:-@).should == @zero - @zero_neg.send(:-@).sign.should == 1 + @zero.send(:-@).should == @zero + @zero.send(:-@).sign.should == -1 + @zero_pos.send(:-@).should == @zero + @zero_pos.send(:-@).sign.should == -1 + @zero_neg.send(:-@).should == @zero + @zero_neg.send(:-@).sign.should == 1 - @nan.send(:-@).should.nan? - end + @nan.send(:-@).should.nan? end end diff --git a/spec/ruby/library/bigdecimal/uplus_spec.rb b/spec/ruby/library/bigdecimal/uplus_spec.rb index 9610593401cfab..77483046b749b2 100644 --- a/spec/ruby/library/bigdecimal/uplus_spec.rb +++ b/spec/ruby/library/bigdecimal/uplus_spec.rb @@ -1,20 +1,17 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#+@" do - it "returns the same value with same sign (twos complement)" do - first = BigDecimal("34.56") - first.send(:+@).should == first - second = BigDecimal("-34.56") - second.send(:+@).should == second - third = BigDecimal("0.0") - third.send(:+@).should == third - fourth = BigDecimal("2E1000000") - fourth.send(:+@).should == fourth - fifth = BigDecimal("123456789E-1000000") - fifth.send(:+@).should == fifth - end +describe "BigDecimal#+@" do + it "returns the same value with same sign (twos complement)" do + first = BigDecimal("34.56") + first.send(:+@).should == first + second = BigDecimal("-34.56") + second.send(:+@).should == second + third = BigDecimal("0.0") + third.send(:+@).should == third + fourth = BigDecimal("2E1000000") + fourth.send(:+@).should == fourth + fifth = BigDecimal("123456789E-1000000") + fifth.send(:+@).should == fifth end end diff --git a/spec/ruby/library/bigdecimal/util_spec.rb b/spec/ruby/library/bigdecimal/util_spec.rb index 4ef82935d15500..fc67fcf200bebd 100644 --- a/spec/ruby/library/bigdecimal/util_spec.rb +++ b/spec/ruby/library/bigdecimal/util_spec.rb @@ -1,43 +1,40 @@ require_relative '../../spec_helper' +require 'bigdecimal' +require 'bigdecimal/util' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - require 'bigdecimal/util' - - describe "BigDecimal's util method definitions" do - describe "#to_d" do - it "should define #to_d on Integer" do - 42.to_d.should == BigDecimal(42) - end +describe "BigDecimal's util method definitions" do + describe "#to_d" do + it "should define #to_d on Integer" do + 42.to_d.should == BigDecimal(42) + end - it "should define #to_d on Float" do - 0.5.to_d.should == BigDecimal(0.5, Float::DIG) - 1.234.to_d(2).should == BigDecimal(1.234, 2) - end + it "should define #to_d on Float" do + 0.5.to_d.should == BigDecimal(0.5, Float::DIG) + 1.234.to_d(2).should == BigDecimal(1.234, 2) + end - it "should define #to_d on String" do - "0.5".to_d.should == BigDecimal(0.5, Float::DIG) - "45.67 degrees".to_d.should == BigDecimal(45.67, Float::DIG) - end + it "should define #to_d on String" do + "0.5".to_d.should == BigDecimal(0.5, Float::DIG) + "45.67 degrees".to_d.should == BigDecimal(45.67, Float::DIG) + end - it "should define #to_d on BigDecimal" do - bd = BigDecimal("3.14") - bd.to_d.should equal(bd) - end + it "should define #to_d on BigDecimal" do + bd = BigDecimal("3.14") + bd.to_d.should equal(bd) + end - it "should define #to_d on Rational" do - Rational(22, 7).to_d(3).should == BigDecimal(3.14, 3) - end + it "should define #to_d on Rational" do + Rational(22, 7).to_d(3).should == BigDecimal(3.14, 3) + end - it "should define #to_d on nil" do - nil.to_d.should == BigDecimal(0) - end + it "should define #to_d on nil" do + nil.to_d.should == BigDecimal(0) end + end - describe "#to_digits" do - it "should define #to_digits on BigDecimal" do - BigDecimal("3.14").to_digits.should == "3.14" - end + describe "#to_digits" do + it "should define #to_digits on BigDecimal" do + BigDecimal("3.14").to_digits.should == "3.14" end end end diff --git a/spec/ruby/library/bigdecimal/zero_spec.rb b/spec/ruby/library/bigdecimal/zero_spec.rb index 94bd7d1a40722c..2563210939e251 100644 --- a/spec/ruby/library/bigdecimal/zero_spec.rb +++ b/spec/ruby/library/bigdecimal/zero_spec.rb @@ -1,30 +1,27 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' +describe "BigDecimal#zero?" do - describe "BigDecimal#zero?" do - - it "returns true if self does equal zero" do - really_small_zero = BigDecimal("0E-200000000") - really_big_zero = BigDecimal("0E200000000000") - really_small_zero.should.zero? - really_big_zero.should.zero? - BigDecimal("0.000000000000000000000000").should.zero? - BigDecimal("0").should.zero? - BigDecimal("0E0").should.zero? - BigDecimal("+0").should.zero? - BigDecimal("-0").should.zero? - end - - it "returns false otherwise" do - BigDecimal("0000000001").should_not.zero? - BigDecimal("2E40001").should_not.zero? - BigDecimal("3E-20001").should_not.zero? - BigDecimal("Infinity").should_not.zero? - BigDecimal("-Infinity").should_not.zero? - BigDecimal("NaN").should_not.zero? - end + it "returns true if self does equal zero" do + really_small_zero = BigDecimal("0E-200000000") + really_big_zero = BigDecimal("0E200000000000") + really_small_zero.should.zero? + really_big_zero.should.zero? + BigDecimal("0.000000000000000000000000").should.zero? + BigDecimal("0").should.zero? + BigDecimal("0E0").should.zero? + BigDecimal("+0").should.zero? + BigDecimal("-0").should.zero? + end + it "returns false otherwise" do + BigDecimal("0000000001").should_not.zero? + BigDecimal("2E40001").should_not.zero? + BigDecimal("3E-20001").should_not.zero? + BigDecimal("Infinity").should_not.zero? + BigDecimal("-Infinity").should_not.zero? + BigDecimal("NaN").should_not.zero? end + end diff --git a/spec/ruby/library/bigmath/log_spec.rb b/spec/ruby/library/bigmath/log_spec.rb index 22df38bb294ae0..2ffbf8b6b9cbdf 100644 --- a/spec/ruby/library/bigmath/log_spec.rb +++ b/spec/ruby/library/bigmath/log_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' +require 'bigdecimal' -ruby_version_is ""..."3.4" do - require 'bigdecimal' - - describe "BigDecimal#log" do - it "handles high-precision Rational arguments" do - result = BigDecimal('0.22314354220170971436137296411949880462556361100856391620766259404746040597133837784E0') - r = Rational(1_234_567_890, 987_654_321) - BigMath.log(r, 50).should == result - end +describe "BigDecimal#log" do + it "handles high-precision Rational arguments" do + result = BigDecimal('0.22314354220170971436137296411949880462556361100856391620766259404746040597133837784E0') + r = Rational(1_234_567_890, 987_654_321) + BigMath.log(r, 50).should == result end end diff --git a/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb index d3f667c602e84e..599e640624e6ad 100644 --- a/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb +++ b/spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::BasicWriter#close_on_terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::BasicWriter#close_on_terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/basicwriter/initialize_spec.rb b/spec/ruby/library/csv/basicwriter/initialize_spec.rb index af86bc177bd0f5..2c13c93f2eb343 100644 --- a/spec/ruby/library/csv/basicwriter/initialize_spec.rb +++ b/spec/ruby/library/csv/basicwriter/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::BasicWriter#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::BasicWriter#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/basicwriter/terminate_spec.rb b/spec/ruby/library/csv/basicwriter/terminate_spec.rb index 5396295a616859..8c53db3f0baaf4 100644 --- a/spec/ruby/library/csv/basicwriter/terminate_spec.rb +++ b/spec/ruby/library/csv/basicwriter/terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::BasicWriter#terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::BasicWriter#terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/cell/data_spec.rb b/spec/ruby/library/csv/cell/data_spec.rb index 14f49ace96403c..aa034b0b62e84a 100644 --- a/spec/ruby/library/csv/cell/data_spec.rb +++ b/spec/ruby/library/csv/cell/data_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Cell#data" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Cell#data" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/cell/initialize_spec.rb b/spec/ruby/library/csv/cell/initialize_spec.rb index ff3ac55470889b..c9e506676ca9e4 100644 --- a/spec/ruby/library/csv/cell/initialize_spec.rb +++ b/spec/ruby/library/csv/cell/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Cell#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Cell#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/foreach_spec.rb b/spec/ruby/library/csv/foreach_spec.rb index c8aa1b8009157b..36b3cd152fa07a 100644 --- a/spec/ruby/library/csv/foreach_spec.rb +++ b/spec/ruby/library/csv/foreach_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV.foreach" do - it "needs to be reviewed for spec completeness" - end +describe "CSV.foreach" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/generate_line_spec.rb b/spec/ruby/library/csv/generate_line_spec.rb index 0830bbdb63fdd7..656365b109e9c3 100644 --- a/spec/ruby/library/csv/generate_line_spec.rb +++ b/spec/ruby/library/csv/generate_line_spec.rb @@ -1,33 +1,30 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' +describe "CSV.generate_line" do - describe "CSV.generate_line" do - - it "generates an empty string" do - result = CSV.generate_line([]) - result.should == "\n" - end + it "generates an empty string" do + result = CSV.generate_line([]) + result.should == "\n" + end - it "generates the string 'foo,bar'" do - result = CSV.generate_line(["foo", "bar"]) - result.should == "foo,bar\n" - end + it "generates the string 'foo,bar'" do + result = CSV.generate_line(["foo", "bar"]) + result.should == "foo,bar\n" + end - it "generates the string 'foo;bar'" do - result = CSV.generate_line(["foo", "bar"], col_sep: ?;) - result.should == "foo;bar\n" - end + it "generates the string 'foo;bar'" do + result = CSV.generate_line(["foo", "bar"], col_sep: ?;) + result.should == "foo;bar\n" + end - it "generates the string 'foo,,bar'" do - result = CSV.generate_line(["foo", nil, "bar"]) - result.should == "foo,,bar\n" - end + it "generates the string 'foo,,bar'" do + result = CSV.generate_line(["foo", nil, "bar"]) + result.should == "foo,,bar\n" + end - it "generates the string 'foo;;bar'" do - result = CSV.generate_line(["foo", nil, "bar"], col_sep: ?;) - result.should == "foo;;bar\n" - end + it "generates the string 'foo;;bar'" do + result = CSV.generate_line(["foo", nil, "bar"], col_sep: ?;) + result.should == "foo;;bar\n" end end diff --git a/spec/ruby/library/csv/generate_row_spec.rb b/spec/ruby/library/csv/generate_row_spec.rb index aff5332a9e916c..79dfc34000a22a 100644 --- a/spec/ruby/library/csv/generate_row_spec.rb +++ b/spec/ruby/library/csv/generate_row_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV.generate_row" do - it "needs to be reviewed for spec completeness" - end +describe "CSV.generate_row" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/generate_spec.rb b/spec/ruby/library/csv/generate_spec.rb index de476d437f28e4..0a1e3d9604fa7b 100644 --- a/spec/ruby/library/csv/generate_spec.rb +++ b/spec/ruby/library/csv/generate_spec.rb @@ -1,35 +1,32 @@ require_relative '../../spec_helper' +require 'csv' +require 'tempfile' -ruby_version_is ""..."3.4" do - require 'csv' - require 'tempfile' +describe "CSV.generate" do - describe "CSV.generate" do - - it "returns CSV string" do - csv_str = CSV.generate do |csv| - csv.add_row [1, 2, 3] - csv << [4, 5, 6] - end - csv_str.should == "1,2,3\n4,5,6\n" + it "returns CSV string" do + csv_str = CSV.generate do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] end + csv_str.should == "1,2,3\n4,5,6\n" + end - it "accepts a col separator" do - csv_str = CSV.generate(col_sep: ";") do |csv| - csv.add_row [1, 2, 3] - csv << [4, 5, 6] - end - csv_str.should == "1;2;3\n4;5;6\n" + it "accepts a col separator" do + csv_str = CSV.generate(col_sep: ";") do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] end + csv_str.should == "1;2;3\n4;5;6\n" + end - it "appends and returns the argument itself" do - str = "" - csv_str = CSV.generate(str) do |csv| - csv.add_row [1, 2, 3] - csv << [4, 5, 6] - end - csv_str.should equal str - str.should == "1,2,3\n4,5,6\n" + it "appends and returns the argument itself" do + str = "" + csv_str = CSV.generate(str) do |csv| + csv.add_row [1, 2, 3] + csv << [4, 5, 6] end + csv_str.should equal str + str.should == "1,2,3\n4,5,6\n" end end diff --git a/spec/ruby/library/csv/iobuf/close_spec.rb b/spec/ruby/library/csv/iobuf/close_spec.rb index 5711a816ecd2b6..e2fda86080dd79 100644 --- a/spec/ruby/library/csv/iobuf/close_spec.rb +++ b/spec/ruby/library/csv/iobuf/close_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOBuf#close" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOBuf#close" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/iobuf/initialize_spec.rb b/spec/ruby/library/csv/iobuf/initialize_spec.rb index 0073bd44813be0..f08e82548a0fdb 100644 --- a/spec/ruby/library/csv/iobuf/initialize_spec.rb +++ b/spec/ruby/library/csv/iobuf/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOBuf#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOBuf#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/iobuf/read_spec.rb b/spec/ruby/library/csv/iobuf/read_spec.rb index e74d8178ddf185..b45334ee2aea4d 100644 --- a/spec/ruby/library/csv/iobuf/read_spec.rb +++ b/spec/ruby/library/csv/iobuf/read_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOBuf#read" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOBuf#read" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/iobuf/terminate_spec.rb b/spec/ruby/library/csv/iobuf/terminate_spec.rb index e4ab00bc4f249c..69289e960c27f1 100644 --- a/spec/ruby/library/csv/iobuf/terminate_spec.rb +++ b/spec/ruby/library/csv/iobuf/terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOBuf#terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOBuf#terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb index 25026091aa2ffa..4887ade1a1f64e 100644 --- a/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb +++ b/spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOReader#close_on_terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOReader#close_on_terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/ioreader/get_row_spec.rb b/spec/ruby/library/csv/ioreader/get_row_spec.rb index c2258738d11c56..5fd2178b1b1002 100644 --- a/spec/ruby/library/csv/ioreader/get_row_spec.rb +++ b/spec/ruby/library/csv/ioreader/get_row_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOReader#get_row" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOReader#get_row" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/ioreader/initialize_spec.rb b/spec/ruby/library/csv/ioreader/initialize_spec.rb index 4f3a75cc8e356c..4e8c0964dfb54c 100644 --- a/spec/ruby/library/csv/ioreader/initialize_spec.rb +++ b/spec/ruby/library/csv/ioreader/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOReader#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOReader#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/ioreader/terminate_spec.rb b/spec/ruby/library/csv/ioreader/terminate_spec.rb index 2403d02a9f23fd..676cd03a0fff81 100644 --- a/spec/ruby/library/csv/ioreader/terminate_spec.rb +++ b/spec/ruby/library/csv/ioreader/terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::IOReader#terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::IOReader#terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/liberal_parsing_spec.rb b/spec/ruby/library/csv/liberal_parsing_spec.rb index d344229d5b991f..98786580271979 100644 --- a/spec/ruby/library/csv/liberal_parsing_spec.rb +++ b/spec/ruby/library/csv/liberal_parsing_spec.rb @@ -1,22 +1,19 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV#liberal_parsing?" do - it "returns true if illegal input is handled" do - csv = CSV.new("", liberal_parsing: true) - csv.should.liberal_parsing? - end +describe "CSV#liberal_parsing?" do + it "returns true if illegal input is handled" do + csv = CSV.new("", liberal_parsing: true) + csv.should.liberal_parsing? + end - it "returns false if illegal input is not handled" do - csv = CSV.new("", liberal_parsing: false) - csv.should_not.liberal_parsing? - end + it "returns false if illegal input is not handled" do + csv = CSV.new("", liberal_parsing: false) + csv.should_not.liberal_parsing? + end - it "returns false by default" do - csv = CSV.new("") - csv.should_not.liberal_parsing? - end + it "returns false by default" do + csv = CSV.new("") + csv.should_not.liberal_parsing? end end diff --git a/spec/ruby/library/csv/open_spec.rb b/spec/ruby/library/csv/open_spec.rb index 4b5f934e523cc1..2d310cda3e58d6 100644 --- a/spec/ruby/library/csv/open_spec.rb +++ b/spec/ruby/library/csv/open_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV.open" do - it "needs to be reviewed for spec completeness" - end +describe "CSV.open" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/parse_spec.rb b/spec/ruby/library/csv/parse_spec.rb index 921ee0648d4481..ef5d4ea3ca1f43 100644 --- a/spec/ruby/library/csv/parse_spec.rb +++ b/spec/ruby/library/csv/parse_spec.rb @@ -1,96 +1,93 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV.parse" do - - it "parses '' into []" do - result = CSV.parse '' - result.should be_kind_of(Array) - result.should == [] - end - - it "parses '\n' into [[]]" do - result = CSV.parse "\n" - result.should == [[]] - end - - it "parses 'foo' into [['foo']]" do - result = CSV.parse 'foo' - result.should == [['foo']] - end - - it "parses 'foo,bar,baz' into [['foo','bar','baz']]" do - result = CSV.parse 'foo,bar,baz' - result.should == [['foo','bar','baz']] - end - - it "parses 'foo,baz' into [[foo,nil,baz]]" do - result = CSV.parse 'foo,,baz' - result.should == [['foo',nil,'baz']] - end - - it "parses '\nfoo' into [[],['foo']]" do - result = CSV.parse "\nfoo" - result.should == [[],['foo']] - end - - it "parses 'foo\n' into [['foo']]" do - result = CSV.parse "foo\n" - result.should == [['foo']] - end - - it "parses 'foo\nbar' into [['foo'],['bar']]" do - result = CSV.parse "foo\nbar" - result.should == [['foo'],['bar']] - end - - it "parses 'foo,bar\nbaz,quz' into [['foo','bar'],['baz','quz']]" do - result = CSV.parse "foo,bar\nbaz,quz" - result.should == [['foo','bar'],['baz','quz']] - end - - it "parses 'foo,bar'\nbaz' into [['foo','bar'],['baz']]" do - result = CSV.parse "foo,bar\nbaz" - result.should == [['foo','bar'],['baz']] - end - - it "parses 'foo\nbar,baz' into [['foo'],['bar','baz']]" do - result = CSV.parse "foo\nbar,baz" - result.should == [['foo'],['bar','baz']] - end - - it "parses '\n\nbar' into [[],[],'bar']]" do - result = CSV.parse "\n\nbar" - result.should == [[],[],['bar']] - end - - it "parses 'foo' into [['foo']] with a separator of ;" do - result = CSV.parse "foo", col_sep: ?; - result.should == [['foo']] - end - - it "parses 'foo;bar' into [['foo','bar']] with a separator of ;" do - result = CSV.parse "foo;bar", col_sep: ?; - result.should == [['foo','bar']] - end - - it "parses 'foo;bar\nbaz;quz' into [['foo','bar'],['baz','quz']] with a separator of ;" do - result = CSV.parse "foo;bar\nbaz;quz", col_sep: ?; - result.should == [['foo','bar'],['baz','quz']] - end - - it "raises CSV::MalformedCSVError exception if input is illegal" do - -> { - CSV.parse('"quoted" field') - }.should raise_error(CSV::MalformedCSVError) - end - - it "handles illegal input with the liberal_parsing option" do - illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' - result = CSV.parse(illegal_input, liberal_parsing: true) - result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] - end +describe "CSV.parse" do + + it "parses '' into []" do + result = CSV.parse '' + result.should be_kind_of(Array) + result.should == [] + end + + it "parses '\n' into [[]]" do + result = CSV.parse "\n" + result.should == [[]] + end + + it "parses 'foo' into [['foo']]" do + result = CSV.parse 'foo' + result.should == [['foo']] + end + + it "parses 'foo,bar,baz' into [['foo','bar','baz']]" do + result = CSV.parse 'foo,bar,baz' + result.should == [['foo','bar','baz']] + end + + it "parses 'foo,baz' into [[foo,nil,baz]]" do + result = CSV.parse 'foo,,baz' + result.should == [['foo',nil,'baz']] + end + + it "parses '\nfoo' into [[],['foo']]" do + result = CSV.parse "\nfoo" + result.should == [[],['foo']] + end + + it "parses 'foo\n' into [['foo']]" do + result = CSV.parse "foo\n" + result.should == [['foo']] + end + + it "parses 'foo\nbar' into [['foo'],['bar']]" do + result = CSV.parse "foo\nbar" + result.should == [['foo'],['bar']] + end + + it "parses 'foo,bar\nbaz,quz' into [['foo','bar'],['baz','quz']]" do + result = CSV.parse "foo,bar\nbaz,quz" + result.should == [['foo','bar'],['baz','quz']] + end + + it "parses 'foo,bar'\nbaz' into [['foo','bar'],['baz']]" do + result = CSV.parse "foo,bar\nbaz" + result.should == [['foo','bar'],['baz']] + end + + it "parses 'foo\nbar,baz' into [['foo'],['bar','baz']]" do + result = CSV.parse "foo\nbar,baz" + result.should == [['foo'],['bar','baz']] + end + + it "parses '\n\nbar' into [[],[],'bar']]" do + result = CSV.parse "\n\nbar" + result.should == [[],[],['bar']] + end + + it "parses 'foo' into [['foo']] with a separator of ;" do + result = CSV.parse "foo", col_sep: ?; + result.should == [['foo']] + end + + it "parses 'foo;bar' into [['foo','bar']] with a separator of ;" do + result = CSV.parse "foo;bar", col_sep: ?; + result.should == [['foo','bar']] + end + + it "parses 'foo;bar\nbaz;quz' into [['foo','bar'],['baz','quz']] with a separator of ;" do + result = CSV.parse "foo;bar\nbaz;quz", col_sep: ?; + result.should == [['foo','bar'],['baz','quz']] + end + + it "raises CSV::MalformedCSVError exception if input is illegal" do + -> { + CSV.parse('"quoted" field') + }.should raise_error(CSV::MalformedCSVError) + end + + it "handles illegal input with the liberal_parsing option" do + illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + result = CSV.parse(illegal_input, liberal_parsing: true) + result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] end end diff --git a/spec/ruby/library/csv/read_spec.rb b/spec/ruby/library/csv/read_spec.rb index e455a40f739902..2e6bb65d56c0d6 100644 --- a/spec/ruby/library/csv/read_spec.rb +++ b/spec/ruby/library/csv/read_spec.rb @@ -1,9 +1,6 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV.read" do - it "needs to be reviewed for spec completeness" - end +describe "CSV.read" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/readlines_spec.rb b/spec/ruby/library/csv/readlines_spec.rb index 9ca08592522f1c..14dea34381da2b 100644 --- a/spec/ruby/library/csv/readlines_spec.rb +++ b/spec/ruby/library/csv/readlines_spec.rb @@ -1,38 +1,35 @@ require_relative '../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' +describe "CSV.readlines" do + it "needs to be reviewed for spec completeness" +end - describe "CSV.readlines" do - it "needs to be reviewed for spec completeness" +describe "CSV#readlines" do + it "returns an Array of Array containing each element in a one-line CSV file" do + file = CSV.new "a, b, c" + file.readlines.should == [["a", " b", " c"]] end - describe "CSV#readlines" do - it "returns an Array of Array containing each element in a one-line CSV file" do - file = CSV.new "a, b, c" - file.readlines.should == [["a", " b", " c"]] - end - - it "returns an Array of Arrays containing each element in a multi-line CSV file" do - file = CSV.new "a, b, c\nd, e, f" - file.readlines.should == [["a", " b", " c"], ["d", " e", " f"]] - end + it "returns an Array of Arrays containing each element in a multi-line CSV file" do + file = CSV.new "a, b, c\nd, e, f" + file.readlines.should == [["a", " b", " c"], ["d", " e", " f"]] + end - it "returns nil for a missing value" do - file = CSV.new "a,, b, c" - file.readlines.should == [["a", nil, " b", " c"]] - end + it "returns nil for a missing value" do + file = CSV.new "a,, b, c" + file.readlines.should == [["a", nil, " b", " c"]] + end - it "raises CSV::MalformedCSVError exception if input is illegal" do - csv = CSV.new('"quoted" field') - -> { csv.readlines }.should raise_error(CSV::MalformedCSVError) - end + it "raises CSV::MalformedCSVError exception if input is illegal" do + csv = CSV.new('"quoted" field') + -> { csv.readlines }.should raise_error(CSV::MalformedCSVError) + end - it "handles illegal input with the liberal_parsing option" do - illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' - csv = CSV.new(illegal_input, liberal_parsing: true) - result = csv.readlines - result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] - end + it "handles illegal input with the liberal_parsing option" do + illegal_input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + csv = CSV.new(illegal_input, liberal_parsing: true) + result = csv.readlines + result.should == [["Johnson, Dwayne", 'Dwayne "The Rock" Johnson']] end end diff --git a/spec/ruby/library/csv/streambuf/add_buf_spec.rb b/spec/ruby/library/csv/streambuf/add_buf_spec.rb index 42a9eb68beb450..58c530c5007a23 100644 --- a/spec/ruby/library/csv/streambuf/add_buf_spec.rb +++ b/spec/ruby/library/csv/streambuf/add_buf_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#add_buf" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#add_buf" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/buf_size_spec.rb b/spec/ruby/library/csv/streambuf/buf_size_spec.rb index 9a9d34bdefa73a..1793c8b65e3e2f 100644 --- a/spec/ruby/library/csv/streambuf/buf_size_spec.rb +++ b/spec/ruby/library/csv/streambuf/buf_size_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#buf_size" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#buf_size" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/drop_spec.rb b/spec/ruby/library/csv/streambuf/drop_spec.rb index befe0da2f8e5e9..448f0a2196bb87 100644 --- a/spec/ruby/library/csv/streambuf/drop_spec.rb +++ b/spec/ruby/library/csv/streambuf/drop_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#drop" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#drop" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/element_reference_spec.rb b/spec/ruby/library/csv/streambuf/element_reference_spec.rb index 5ff42beeeed6b3..5a75901830eddd 100644 --- a/spec/ruby/library/csv/streambuf/element_reference_spec.rb +++ b/spec/ruby/library/csv/streambuf/element_reference_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#[]" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#[]" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/get_spec.rb b/spec/ruby/library/csv/streambuf/get_spec.rb index 0aa5e72e8f1e83..2255e55e918c6d 100644 --- a/spec/ruby/library/csv/streambuf/get_spec.rb +++ b/spec/ruby/library/csv/streambuf/get_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#get" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#get" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb index f38cb7d8f8cbf9..563b8b2d4ae288 100644 --- a/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb +++ b/spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#idx_is_eos?" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#idx_is_eos?" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/initialize_spec.rb b/spec/ruby/library/csv/streambuf/initialize_spec.rb index 3655b02e25bc0a..1273c98094f93a 100644 --- a/spec/ruby/library/csv/streambuf/initialize_spec.rb +++ b/spec/ruby/library/csv/streambuf/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/is_eos_spec.rb b/spec/ruby/library/csv/streambuf/is_eos_spec.rb index 9891536feadf1f..a0a3c1e0b0dc24 100644 --- a/spec/ruby/library/csv/streambuf/is_eos_spec.rb +++ b/spec/ruby/library/csv/streambuf/is_eos_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#is_eos?" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#is_eos?" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/read_spec.rb b/spec/ruby/library/csv/streambuf/read_spec.rb index 7a82d0d13d5b0d..cf98c5340978a6 100644 --- a/spec/ruby/library/csv/streambuf/read_spec.rb +++ b/spec/ruby/library/csv/streambuf/read_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#read" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#read" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/rel_buf_spec.rb b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb index 2994f5c950b8b0..548e347200f713 100644 --- a/spec/ruby/library/csv/streambuf/rel_buf_spec.rb +++ b/spec/ruby/library/csv/streambuf/rel_buf_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#rel_buf" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#rel_buf" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/streambuf/terminate_spec.rb b/spec/ruby/library/csv/streambuf/terminate_spec.rb index e720d83703a7ae..247b33184af995 100644 --- a/spec/ruby/library/csv/streambuf/terminate_spec.rb +++ b/spec/ruby/library/csv/streambuf/terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StreamBuf#terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StreamBuf#terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/stringreader/get_row_spec.rb b/spec/ruby/library/csv/stringreader/get_row_spec.rb index b4a13cf38a6c50..5cc34470613333 100644 --- a/spec/ruby/library/csv/stringreader/get_row_spec.rb +++ b/spec/ruby/library/csv/stringreader/get_row_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StringReader#get_row" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StringReader#get_row" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/stringreader/initialize_spec.rb b/spec/ruby/library/csv/stringreader/initialize_spec.rb index 2c71d8a8b80701..4e3634847e0e5a 100644 --- a/spec/ruby/library/csv/stringreader/initialize_spec.rb +++ b/spec/ruby/library/csv/stringreader/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::StringReader#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::StringReader#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/add_row_spec.rb b/spec/ruby/library/csv/writer/add_row_spec.rb index 27af76a6372408..2f074b45db627a 100644 --- a/spec/ruby/library/csv/writer/add_row_spec.rb +++ b/spec/ruby/library/csv/writer/add_row_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer#add_row" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer#add_row" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/append_spec.rb b/spec/ruby/library/csv/writer/append_spec.rb index 1c968e2b97da3b..4e1f6728c2b8f8 100644 --- a/spec/ruby/library/csv/writer/append_spec.rb +++ b/spec/ruby/library/csv/writer/append_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer#<<" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer#<<" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/close_spec.rb b/spec/ruby/library/csv/writer/close_spec.rb index 65292dc4d0ebae..1a87094bb7c150 100644 --- a/spec/ruby/library/csv/writer/close_spec.rb +++ b/spec/ruby/library/csv/writer/close_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer#close" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer#close" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/create_spec.rb b/spec/ruby/library/csv/writer/create_spec.rb index 0af9f44ff4e3d0..a4514d55780e33 100644 --- a/spec/ruby/library/csv/writer/create_spec.rb +++ b/spec/ruby/library/csv/writer/create_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer.create" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer.create" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/generate_spec.rb b/spec/ruby/library/csv/writer/generate_spec.rb index 8200ae566d3535..6ea916177728d9 100644 --- a/spec/ruby/library/csv/writer/generate_spec.rb +++ b/spec/ruby/library/csv/writer/generate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer.generate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer.generate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/initialize_spec.rb b/spec/ruby/library/csv/writer/initialize_spec.rb index 574f39978a0fd0..6bba8f8d0a2996 100644 --- a/spec/ruby/library/csv/writer/initialize_spec.rb +++ b/spec/ruby/library/csv/writer/initialize_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer#initialize" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer#initialize" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/csv/writer/terminate_spec.rb b/spec/ruby/library/csv/writer/terminate_spec.rb index 56b78e10888b43..77136dd018d249 100644 --- a/spec/ruby/library/csv/writer/terminate_spec.rb +++ b/spec/ruby/library/csv/writer/terminate_spec.rb @@ -1,9 +1,6 @@ require_relative '../../../spec_helper' +require 'csv' -ruby_version_is ""..."3.4" do - require 'csv' - - describe "CSV::Writer#terminate" do - it "needs to be reviewed for spec completeness" - end +describe "CSV::Writer#terminate" do + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/library/drb/start_service_spec.rb b/spec/ruby/library/drb/start_service_spec.rb index 1ed2081d80e266..016c8b2cffbb73 100644 --- a/spec/ruby/library/drb/start_service_spec.rb +++ b/spec/ruby/library/drb/start_service_spec.rb @@ -1,31 +1,28 @@ require_relative '../../spec_helper' +require_relative 'fixtures/test_server' +require 'drb' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/test_server' - require 'drb' - - describe "DRb.start_service" do - before :each do - @server = DRb.start_service("druby://localhost:0", TestServer.new) - end +describe "DRb.start_service" do + before :each do + @server = DRb.start_service("druby://localhost:0", TestServer.new) + end - after :each do - DRb.stop_service if @server - end + after :each do + DRb.stop_service if @server + end - it "runs a basic remote call" do - DRb.current_server.should == @server - obj = DRbObject.new(nil, @server.uri) - obj.add(1,2,3).should == 6 - end + it "runs a basic remote call" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add(1,2,3).should == 6 + end - it "runs a basic remote call passing a block" do - DRb.current_server.should == @server - obj = DRbObject.new(nil, @server.uri) - obj.add_yield(2) do |i| - i.should == 2 - i+1 - end.should == 4 - end + it "runs a basic remote call passing a block" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add_yield(2) do |i| + i.should == 2 + i+1 + end.should == 4 end end diff --git a/spec/ruby/library/getoptlong/each_option_spec.rb b/spec/ruby/library/getoptlong/each_option_spec.rb index 87dd825264c4d6..c6d82af86d4107 100644 --- a/spec/ruby/library/getoptlong/each_option_spec.rb +++ b/spec/ruby/library/getoptlong/each_option_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/each' -ruby_version_is ""..."3.4" do - require 'getoptlong' - require_relative 'shared/each' - - describe "GetoptLong#each_option" do - it_behaves_like :getoptlong_each, :each_option - end +describe "GetoptLong#each_option" do + it_behaves_like :getoptlong_each, :each_option end diff --git a/spec/ruby/library/getoptlong/each_spec.rb b/spec/ruby/library/getoptlong/each_spec.rb index d40b17d8cb6055..d9022f02af2ace 100644 --- a/spec/ruby/library/getoptlong/each_spec.rb +++ b/spec/ruby/library/getoptlong/each_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/each' -ruby_version_is ""..."3.4" do - require 'getoptlong' - require_relative 'shared/each' - - describe "GetoptLong#each" do - it_behaves_like :getoptlong_each, :each - end +describe "GetoptLong#each" do + it_behaves_like :getoptlong_each, :each end diff --git a/spec/ruby/library/getoptlong/error_message_spec.rb b/spec/ruby/library/getoptlong/error_message_spec.rb index bcca728720be20..1ed9419f6cc7df 100644 --- a/spec/ruby/library/getoptlong/error_message_spec.rb +++ b/spec/ruby/library/getoptlong/error_message_spec.rb @@ -1,26 +1,23 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' +describe "GetoptLong#error_message" do + it "returns nil if no error occurred" do + opts = GetoptLong.new + opts.error_message.should == nil + end - describe "GetoptLong#error_message" do - it "returns nil if no error occurred" do + it "returns the error message of the last error that occurred" do + argv [] do opts = GetoptLong.new - opts.error_message.should == nil - end - - it "returns the error message of the last error that occurred" do - argv [] do - opts = GetoptLong.new - opts.quiet = true - opts.get - -> { - opts.ordering = GetoptLong::PERMUTE - }.should raise_error(ArgumentError) { |e| - e.message.should == "argument error" - opts.error_message.should == "argument error" - } - end + opts.quiet = true + opts.get + -> { + opts.ordering = GetoptLong::PERMUTE + }.should raise_error(ArgumentError) { |e| + e.message.should == "argument error" + opts.error_message.should == "argument error" + } end end end diff --git a/spec/ruby/library/getoptlong/get_option_spec.rb b/spec/ruby/library/getoptlong/get_option_spec.rb index f8f81bce50ec2d..3cb20443796f0e 100644 --- a/spec/ruby/library/getoptlong/get_option_spec.rb +++ b/spec/ruby/library/getoptlong/get_option_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/get' -ruby_version_is ""..."3.4" do - require 'getoptlong' - require_relative 'shared/get' - - describe "GetoptLong#get_option" do - it_behaves_like :getoptlong_get, :get_option - end +describe "GetoptLong#get_option" do + it_behaves_like :getoptlong_get, :get_option end diff --git a/spec/ruby/library/getoptlong/get_spec.rb b/spec/ruby/library/getoptlong/get_spec.rb index bb901dff786a9d..a8ec586fc9d115 100644 --- a/spec/ruby/library/getoptlong/get_spec.rb +++ b/spec/ruby/library/getoptlong/get_spec.rb @@ -1,10 +1,7 @@ require_relative '../../spec_helper' +require 'getoptlong' +require_relative 'shared/get' -ruby_version_is ""..."3.4" do - require 'getoptlong' - require_relative 'shared/get' - - describe "GetoptLong#get" do - it_behaves_like :getoptlong_get, :get - end +describe "GetoptLong#get" do + it_behaves_like :getoptlong_get, :get end diff --git a/spec/ruby/library/getoptlong/initialize_spec.rb b/spec/ruby/library/getoptlong/initialize_spec.rb index f0e5d605b263ad..782edbd981c544 100644 --- a/spec/ruby/library/getoptlong/initialize_spec.rb +++ b/spec/ruby/library/getoptlong/initialize_spec.rb @@ -1,31 +1,28 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' +describe "GetoptLong#initialize" do + it "sets ordering to REQUIRE_ORDER if ENV['POSIXLY_CORRECT'] is set" do + begin + old_env_value = ENV["POSIXLY_CORRECT"] + ENV["POSIXLY_CORRECT"] = "" - describe "GetoptLong#initialize" do - it "sets ordering to REQUIRE_ORDER if ENV['POSIXLY_CORRECT'] is set" do - begin - old_env_value = ENV["POSIXLY_CORRECT"] - ENV["POSIXLY_CORRECT"] = "" - - opt = GetoptLong.new - opt.ordering.should == GetoptLong::REQUIRE_ORDER - ensure - ENV["POSIXLY_CORRECT"] = old_env_value - end + opt = GetoptLong.new + opt.ordering.should == GetoptLong::REQUIRE_ORDER + ensure + ENV["POSIXLY_CORRECT"] = old_env_value end + end - it "sets ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is not set" do - begin - old_env_value = ENV["POSIXLY_CORRECT"] - ENV["POSIXLY_CORRECT"] = nil + it "sets ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is not set" do + begin + old_env_value = ENV["POSIXLY_CORRECT"] + ENV["POSIXLY_CORRECT"] = nil - opt = GetoptLong.new - opt.ordering.should == GetoptLong::PERMUTE - ensure - ENV["POSIXLY_CORRECT"] = old_env_value - end + opt = GetoptLong.new + opt.ordering.should == GetoptLong::PERMUTE + ensure + ENV["POSIXLY_CORRECT"] = old_env_value end end end diff --git a/spec/ruby/library/getoptlong/ordering_spec.rb b/spec/ruby/library/getoptlong/ordering_spec.rb index b0b5b9be54f035..695d1cafa7511f 100644 --- a/spec/ruby/library/getoptlong/ordering_spec.rb +++ b/spec/ruby/library/getoptlong/ordering_spec.rb @@ -1,41 +1,38 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' - - describe "GetoptLong#ordering=" do - it "raises an ArgumentError if called after processing has started" do - argv [ "--size", "10k", "--verbose" ] do - opts = GetoptLong.new([ '--size', GetoptLong::REQUIRED_ARGUMENT ], - [ '--verbose', GetoptLong::NO_ARGUMENT ]) - opts.quiet = true - opts.get - - -> { - opts.ordering = GetoptLong::PERMUTE - }.should raise_error(ArgumentError) - end - end - - it "raises an ArgumentError if given an invalid value" do - opts = GetoptLong.new +describe "GetoptLong#ordering=" do + it "raises an ArgumentError if called after processing has started" do + argv [ "--size", "10k", "--verbose" ] do + opts = GetoptLong.new([ '--size', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', GetoptLong::NO_ARGUMENT ]) + opts.quiet = true + opts.get -> { - opts.ordering = 12345 + opts.ordering = GetoptLong::PERMUTE }.should raise_error(ArgumentError) end + end - it "does not allow changing ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is set" do - begin - old_env_value = ENV['POSIXLY_CORRECT'] - ENV['POSIXLY_CORRECT'] = "" + it "raises an ArgumentError if given an invalid value" do + opts = GetoptLong.new - opts = GetoptLong.new - opts.ordering = GetoptLong::PERMUTE - opts.ordering.should == GetoptLong::REQUIRE_ORDER - ensure - ENV['POSIXLY_CORRECT'] = old_env_value - end + -> { + opts.ordering = 12345 + }.should raise_error(ArgumentError) + end + + it "does not allow changing ordering to PERMUTE if ENV['POSIXLY_CORRECT'] is set" do + begin + old_env_value = ENV['POSIXLY_CORRECT'] + ENV['POSIXLY_CORRECT'] = "" + + opts = GetoptLong.new + opts.ordering = GetoptLong::PERMUTE + opts.ordering.should == GetoptLong::REQUIRE_ORDER + ensure + ENV['POSIXLY_CORRECT'] = old_env_value end end end diff --git a/spec/ruby/library/getoptlong/set_options_spec.rb b/spec/ruby/library/getoptlong/set_options_spec.rb index 0e77696a9515d1..36b9c579c41d9c 100644 --- a/spec/ruby/library/getoptlong/set_options_spec.rb +++ b/spec/ruby/library/getoptlong/set_options_spec.rb @@ -1,101 +1,98 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' - - describe "GetoptLong#set_options" do - before :each do - @opts = GetoptLong.new - end +describe "GetoptLong#set_options" do + before :each do + @opts = GetoptLong.new + end - it "allows setting command line options" do - argv ["--size", "10k", "-v", "arg1", "arg2"] do - @opts.set_options( - ["--size", GetoptLong::REQUIRED_ARGUMENT], - ["--verbose", "-v", GetoptLong::NO_ARGUMENT] - ) + it "allows setting command line options" do + argv ["--size", "10k", "-v", "arg1", "arg2"] do + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT] + ) - @opts.get.should == ["--size", "10k"] - @opts.get.should == ["--verbose", ""] - @opts.get.should == nil - end + @opts.get.should == ["--size", "10k"] + @opts.get.should == ["--verbose", ""] + @opts.get.should == nil end + end - it "discards previously defined command line options" do - argv ["--size", "10k", "-v", "arg1", "arg2"] do - @opts.set_options( - ["--size", GetoptLong::REQUIRED_ARGUMENT], - ["--verbose", "-v", GetoptLong::NO_ARGUMENT] - ) + it "discards previously defined command line options" do + argv ["--size", "10k", "-v", "arg1", "arg2"] do + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT] + ) - @opts.set_options( - ["-s", "--size", GetoptLong::REQUIRED_ARGUMENT], - ["-v", GetoptLong::NO_ARGUMENT] - ) + @opts.set_options( + ["-s", "--size", GetoptLong::REQUIRED_ARGUMENT], + ["-v", GetoptLong::NO_ARGUMENT] + ) - @opts.get.should == ["-s", "10k"] - @opts.get.should == ["-v", ""] - @opts.get.should == nil - end + @opts.get.should == ["-s", "10k"] + @opts.get.should == ["-v", ""] + @opts.get.should == nil end + end - it "raises an ArgumentError if too many argument flags where given" do - argv [] do - -> { - @opts.set_options(["--size", GetoptLong::NO_ARGUMENT, GetoptLong::REQUIRED_ARGUMENT]) - }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if too many argument flags where given" do + argv [] do + -> { + @opts.set_options(["--size", GetoptLong::NO_ARGUMENT, GetoptLong::REQUIRED_ARGUMENT]) + }.should raise_error(ArgumentError) end + end - it "raises a RuntimeError if processing has already started" do - argv [] do - @opts.get - -> { - @opts.set_options() - }.should raise_error(RuntimeError) - end + it "raises a RuntimeError if processing has already started" do + argv [] do + @opts.get + -> { + @opts.set_options() + }.should raise_error(RuntimeError) end + end - it "raises an ArgumentError if no argument flag was given" do - argv [] do - -> { - @opts.set_options(["--size"]) - }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if no argument flag was given" do + argv [] do + -> { + @opts.set_options(["--size"]) + }.should raise_error(ArgumentError) end + end - it "raises an ArgumentError if one of the given arguments is not an Array" do - argv [] do - -> { - @opts.set_options( - ["--size", GetoptLong::REQUIRED_ARGUMENT], - "test") - }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if one of the given arguments is not an Array" do + argv [] do + -> { + @opts.set_options( + ["--size", GetoptLong::REQUIRED_ARGUMENT], + "test") + }.should raise_error(ArgumentError) end + end - it "raises an ArgumentError if the same option is given twice" do - argv [] do - -> { - @opts.set_options( - ["--size", GetoptLong::NO_ARGUMENT], - ["--size", GetoptLong::OPTIONAL_ARGUMENT]) - }.should raise_error(ArgumentError) + it "raises an ArgumentError if the same option is given twice" do + argv [] do + -> { + @opts.set_options( + ["--size", GetoptLong::NO_ARGUMENT], + ["--size", GetoptLong::OPTIONAL_ARGUMENT]) + }.should raise_error(ArgumentError) - -> { - @opts.set_options( - ["--size", GetoptLong::NO_ARGUMENT], - ["-s", "--size", GetoptLong::OPTIONAL_ARGUMENT]) - }.should raise_error(ArgumentError) - end + -> { + @opts.set_options( + ["--size", GetoptLong::NO_ARGUMENT], + ["-s", "--size", GetoptLong::OPTIONAL_ARGUMENT]) + }.should raise_error(ArgumentError) end + end - it "raises an ArgumentError if the given option is invalid" do - argv [] do - -> { - @opts.set_options(["-size", GetoptLong::NO_ARGUMENT]) - }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if the given option is invalid" do + argv [] do + -> { + @opts.set_options(["-size", GetoptLong::NO_ARGUMENT]) + }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/library/getoptlong/terminate_spec.rb b/spec/ruby/library/getoptlong/terminate_spec.rb index f767fcaa0a392d..a12d1df2ef6b88 100644 --- a/spec/ruby/library/getoptlong/terminate_spec.rb +++ b/spec/ruby/library/getoptlong/terminate_spec.rb @@ -1,33 +1,30 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' - - describe "GetoptLong#terminate" do - before :each do - @opts = GetoptLong.new( - [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ], - [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], - [ '--query', '-q', GetoptLong::NO_ARGUMENT ], - [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ] - ) - end +describe "GetoptLong#terminate" do + before :each do + @opts = GetoptLong.new( + [ '--size', '-s', GetoptLong::REQUIRED_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--query', '-q', GetoptLong::NO_ARGUMENT ], + [ '--check', '--valid', '-c', GetoptLong::NO_ARGUMENT ] + ) + end - it "terminates option processing" do - argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do - @opts.get.should == [ "--size", "10k" ] - @opts.terminate - @opts.get.should == nil - end + it "terminates option processing" do + argv [ "--size", "10k", "-v", "-q", "a.txt", "b.txt" ] do + @opts.get.should == [ "--size", "10k" ] + @opts.terminate + @opts.get.should == nil end + end - it "returns self when option processing is terminated" do - @opts.terminate.should == @opts - end + it "returns self when option processing is terminated" do + @opts.terminate.should == @opts + end - it "returns nil when option processing was already terminated" do - @opts.terminate - @opts.terminate.should == nil - end + it "returns nil when option processing was already terminated" do + @opts.terminate + @opts.terminate.should == nil end end diff --git a/spec/ruby/library/getoptlong/terminated_spec.rb b/spec/ruby/library/getoptlong/terminated_spec.rb index 06c2ce71f4e116..6108a7f6e908e8 100644 --- a/spec/ruby/library/getoptlong/terminated_spec.rb +++ b/spec/ruby/library/getoptlong/terminated_spec.rb @@ -1,20 +1,17 @@ require_relative '../../spec_helper' +require 'getoptlong' -ruby_version_is ""..."3.4" do - require 'getoptlong' +describe "GetoptLong#terminated?" do + it "returns true if option processing has terminated" do + argv [ "--size", "10k" ] do + opts = GetoptLong.new(["--size", GetoptLong::REQUIRED_ARGUMENT]) + opts.should_not.terminated? - describe "GetoptLong#terminated?" do - it "returns true if option processing has terminated" do - argv [ "--size", "10k" ] do - opts = GetoptLong.new(["--size", GetoptLong::REQUIRED_ARGUMENT]) - opts.should_not.terminated? + opts.get.should == ["--size", "10k"] + opts.should_not.terminated? - opts.get.should == ["--size", "10k"] - opts.should_not.terminated? - - opts.get.should == nil - opts.should.terminated? - end + opts.get.should == nil + opts.should.terminated? end end end diff --git a/spec/ruby/library/observer/add_observer_spec.rb b/spec/ruby/library/observer/add_observer_spec.rb index 4c33c647348dcc..5217ae6dc45d71 100644 --- a/spec/ruby/library/observer/add_observer_spec.rb +++ b/spec/ruby/library/observer/add_observer_spec.rb @@ -1,26 +1,23 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' +describe "Observer#add_observer" do - describe "Observer#add_observer" do - - before :each do - @observable = ObservableSpecs.new - @observer = ObserverCallbackSpecs.new - end - - it "adds the observer" do - @observer.value.should == nil - @observable.changed - @observable.notify_observers("test") - @observer.value.should == nil + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end - @observable.add_observer(@observer) - @observable.changed - @observable.notify_observers("test2") - @observer.value.should == "test2" - end + it "adds the observer" do + @observer.value.should == nil + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil + @observable.add_observer(@observer) + @observable.changed + @observable.notify_observers("test2") + @observer.value.should == "test2" end + end diff --git a/spec/ruby/library/observer/count_observers_spec.rb b/spec/ruby/library/observer/count_observers_spec.rb index ab733e4e40b8bc..c93674196d995f 100644 --- a/spec/ruby/library/observer/count_observers_spec.rb +++ b/spec/ruby/library/observer/count_observers_spec.rb @@ -1,26 +1,23 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - - describe "Observer#count_observers" do - before :each do - @observable = ObservableSpecs.new - @observer = ObserverCallbackSpecs.new - @observer2 = ObserverCallbackSpecs.new - end +describe "Observer#count_observers" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + @observer2 = ObserverCallbackSpecs.new + end - it "returns the number of observers" do - @observable.count_observers.should == 0 - @observable.add_observer(@observer) - @observable.count_observers.should == 1 - @observable.add_observer(@observer2) - @observable.count_observers.should == 2 - end + it "returns the number of observers" do + @observable.count_observers.should == 0 + @observable.add_observer(@observer) + @observable.count_observers.should == 1 + @observable.add_observer(@observer2) + @observable.count_observers.should == 2 + end - it "returns the number of unique observers" do - 2.times { @observable.add_observer(@observer) } - @observable.count_observers.should == 1 - end + it "returns the number of unique observers" do + 2.times { @observable.add_observer(@observer) } + @observable.count_observers.should == 1 end end diff --git a/spec/ruby/library/observer/delete_observer_spec.rb b/spec/ruby/library/observer/delete_observer_spec.rb index 83db19bae2c447..52be1a6cbabc85 100644 --- a/spec/ruby/library/observer/delete_observer_spec.rb +++ b/spec/ruby/library/observer/delete_observer_spec.rb @@ -1,22 +1,19 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - - describe "Observer#delete_observer" do - before :each do - @observable = ObservableSpecs.new - @observer = ObserverCallbackSpecs.new - end - - it "deletes the observer" do - @observable.add_observer(@observer) - @observable.delete_observer(@observer) +describe "Observer#delete_observer" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end - @observable.changed - @observable.notify_observers("test") - @observer.value.should == nil - end + it "deletes the observer" do + @observable.add_observer(@observer) + @observable.delete_observer(@observer) + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil end + end diff --git a/spec/ruby/library/observer/delete_observers_spec.rb b/spec/ruby/library/observer/delete_observers_spec.rb index 5e7fe21d741fed..186e93a013c738 100644 --- a/spec/ruby/library/observer/delete_observers_spec.rb +++ b/spec/ruby/library/observer/delete_observers_spec.rb @@ -1,22 +1,19 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' - - describe "Observer#delete_observers" do - before :each do - @observable = ObservableSpecs.new - @observer = ObserverCallbackSpecs.new - end - - it "deletes the observers" do - @observable.add_observer(@observer) - @observable.delete_observers +describe "Observer#delete_observers" do + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + end - @observable.changed - @observable.notify_observers("test") - @observer.value.should == nil - end + it "deletes the observers" do + @observable.add_observer(@observer) + @observable.delete_observers + @observable.changed + @observable.notify_observers("test") + @observer.value.should == nil end + end diff --git a/spec/ruby/library/observer/fixtures/classes.rb b/spec/ruby/library/observer/fixtures/classes.rb index d1f9079963e17c..70cd1b1be277d0 100644 --- a/spec/ruby/library/observer/fixtures/classes.rb +++ b/spec/ruby/library/observer/fixtures/classes.rb @@ -1,19 +1,17 @@ -ruby_version_is ""..."3.4" do - require 'observer' +require 'observer' - class ObserverCallbackSpecs - attr_reader :value +class ObserverCallbackSpecs + attr_reader :value - def initialize - @value = nil - end - - def update(value) - @value = value - end + def initialize + @value = nil end - class ObservableSpecs - include Observable + def update(value) + @value = value end end + +class ObservableSpecs + include Observable +end diff --git a/spec/ruby/library/observer/notify_observers_spec.rb b/spec/ruby/library/observer/notify_observers_spec.rb index 1030ae701ec0d2..31f82e9266d760 100644 --- a/spec/ruby/library/observer/notify_observers_spec.rb +++ b/spec/ruby/library/observer/notify_observers_spec.rb @@ -1,34 +1,31 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is ""..."3.4" do - require_relative 'fixtures/classes' +describe "Observer#notify_observers" do - describe "Observer#notify_observers" do - - before :each do - @observable = ObservableSpecs.new - @observer = ObserverCallbackSpecs.new - @observable.add_observer(@observer) - end - - it "must call changed before notifying observers" do - @observer.value.should == nil - @observable.notify_observers("test") - @observer.value.should == nil - end + before :each do + @observable = ObservableSpecs.new + @observer = ObserverCallbackSpecs.new + @observable.add_observer(@observer) + end - it "verifies observer responds to update" do - -> { - @observable.add_observer(@observable) - }.should raise_error(NoMethodError) - end + it "must call changed before notifying observers" do + @observer.value.should == nil + @observable.notify_observers("test") + @observer.value.should == nil + end - it "receives the callback" do - @observer.value.should == nil - @observable.changed - @observable.notify_observers("test") - @observer.value.should == "test" - end + it "verifies observer responds to update" do + -> { + @observable.add_observer(@observable) + }.should raise_error(NoMethodError) + end + it "receives the callback" do + @observer.value.should == nil + @observable.changed + @observable.notify_observers("test") + @observer.value.should == "test" end + end diff --git a/spec/ruby/library/syslog/alert_spec.rb b/spec/ruby/library/syslog/alert_spec.rb index a3a616bd6d88ba..edff789dc960f7 100644 --- a/spec/ruby/library/syslog/alert_spec.rb +++ b/spec/ruby/library/syslog/alert_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.alert" do - it_behaves_like :syslog_log, :alert - end + describe "Syslog.alert" do + it_behaves_like :syslog_log, :alert end end diff --git a/spec/ruby/library/syslog/close_spec.rb b/spec/ruby/library/syslog/close_spec.rb index 60866de9715b4a..8c3b67c05bd8d6 100644 --- a/spec/ruby/library/syslog/close_spec.rb +++ b/spec/ruby/library/syslog/close_spec.rb @@ -1,60 +1,57 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - platform_is_not :windows do - require 'syslog' - - describe "Syslog.close" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end - - after :each do - Syslog.opened?.should be_false - end - - it "closes the log" do - Syslog.opened?.should be_false - Syslog.open - Syslog.opened?.should be_true - Syslog.close - Syslog.opened?.should be_false - end - - it "raises a RuntimeError if the log's already closed" do - -> { Syslog.close }.should raise_error(RuntimeError) - end - - it "it does not work inside blocks" do - -> { - Syslog.open { |s| s.close } - }.should raise_error(RuntimeError) - Syslog.should_not.opened? - end - - it "sets the identity to nil" do - Syslog.open("rubyspec") - Syslog.ident.should == "rubyspec" - Syslog.close - Syslog.ident.should be_nil - end - - it "sets the options to nil" do - Syslog.open("rubyspec", Syslog::LOG_PID) - Syslog.options.should == Syslog::LOG_PID - Syslog.close - Syslog.options.should == nil - end - - it "sets the facility to nil" do - Syslog.open - Syslog.facility.should == 8 - Syslog.close - Syslog.facility.should == nil - end +platform_is_not :windows do + require 'syslog' + + describe "Syslog.close" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "closes the log" do + Syslog.opened?.should be_false + Syslog.open + Syslog.opened?.should be_true + Syslog.close + Syslog.opened?.should be_false + end + + it "raises a RuntimeError if the log's already closed" do + -> { Syslog.close }.should raise_error(RuntimeError) + end + + it "it does not work inside blocks" do + -> { + Syslog.open { |s| s.close } + }.should raise_error(RuntimeError) + Syslog.should_not.opened? + end + + it "sets the identity to nil" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + Syslog.ident.should be_nil + end + + it "sets the options to nil" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + Syslog.options.should == nil + end + + it "sets the facility to nil" do + Syslog.open + Syslog.facility.should == 8 + Syslog.close + Syslog.facility.should == nil end end end diff --git a/spec/ruby/library/syslog/constants_spec.rb b/spec/ruby/library/syslog/constants_spec.rb index d44d67e2de8cb9..2b9524c53d847d 100644 --- a/spec/ruby/library/syslog/constants_spec.rb +++ b/spec/ruby/library/syslog/constants_spec.rb @@ -1,43 +1,40 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' - - describe "Syslog::Constants" do - platform_is_not :windows, :solaris, :aix do - before :all do - @constants = %w(LOG_AUTHPRIV LOG_USER LOG_LOCAL2 LOG_NOTICE LOG_NDELAY - LOG_SYSLOG LOG_ALERT LOG_FTP LOG_LOCAL5 LOG_ERR LOG_AUTH - LOG_LOCAL1 LOG_ODELAY LOG_NEWS LOG_DAEMON LOG_LOCAL4 - LOG_CRIT LOG_INFO LOG_PERROR LOG_LOCAL0 LOG_CONS LOG_LPR - LOG_LOCAL7 LOG_WARNING LOG_CRON LOG_LOCAL3 LOG_EMERG - LOG_NOWAIT LOG_UUCP LOG_PID LOG_KERN LOG_MAIL LOG_LOCAL6 - LOG_DEBUG) - end + describe "Syslog::Constants" do + platform_is_not :windows, :solaris, :aix do + before :all do + @constants = %w(LOG_AUTHPRIV LOG_USER LOG_LOCAL2 LOG_NOTICE LOG_NDELAY + LOG_SYSLOG LOG_ALERT LOG_FTP LOG_LOCAL5 LOG_ERR LOG_AUTH + LOG_LOCAL1 LOG_ODELAY LOG_NEWS LOG_DAEMON LOG_LOCAL4 + LOG_CRIT LOG_INFO LOG_PERROR LOG_LOCAL0 LOG_CONS LOG_LPR + LOG_LOCAL7 LOG_WARNING LOG_CRON LOG_LOCAL3 LOG_EMERG + LOG_NOWAIT LOG_UUCP LOG_PID LOG_KERN LOG_MAIL LOG_LOCAL6 + LOG_DEBUG) + end - it "includes the Syslog constants" do - @constants.each do |c| - Syslog::Constants.should have_constant(c) - end + it "includes the Syslog constants" do + @constants.each do |c| + Syslog::Constants.should have_constant(c) end end + end - # The masks are defined in + # The masks are defined in - describe "Syslog::Constants.LOG_MASK" do - it "returns the mask value for a priority" do - Syslog::Constants.LOG_MASK(Syslog::LOG_DEBUG).should == 128 - Syslog::Constants.LOG_MASK(Syslog::LOG_WARNING).should == 16 - end + describe "Syslog::Constants.LOG_MASK" do + it "returns the mask value for a priority" do + Syslog::Constants.LOG_MASK(Syslog::LOG_DEBUG).should == 128 + Syslog::Constants.LOG_MASK(Syslog::LOG_WARNING).should == 16 end + end - describe "Syslog::Constants.LOG_UPTO" do - it "returns a mask for the priorities up to a given argument" do - Syslog::Constants.LOG_UPTO(Syslog::LOG_ALERT).should == 3 - Syslog::Constants.LOG_UPTO(Syslog::LOG_DEBUG).should == 255 - end + describe "Syslog::Constants.LOG_UPTO" do + it "returns a mask for the priorities up to a given argument" do + Syslog::Constants.LOG_UPTO(Syslog::LOG_ALERT).should == 3 + Syslog::Constants.LOG_UPTO(Syslog::LOG_DEBUG).should == 255 end end end diff --git a/spec/ruby/library/syslog/crit_spec.rb b/spec/ruby/library/syslog/crit_spec.rb index d7841ac0102c39..5d3904f7196e47 100644 --- a/spec/ruby/library/syslog/crit_spec.rb +++ b/spec/ruby/library/syslog/crit_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.crit" do - it_behaves_like :syslog_log, :crit - end + describe "Syslog.crit" do + it_behaves_like :syslog_log, :crit end end diff --git a/spec/ruby/library/syslog/debug_spec.rb b/spec/ruby/library/syslog/debug_spec.rb index 94e640c74131dc..d03e8a88c9f691 100644 --- a/spec/ruby/library/syslog/debug_spec.rb +++ b/spec/ruby/library/syslog/debug_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.debug" do - it_behaves_like :syslog_log, :debug - end + describe "Syslog.debug" do + it_behaves_like :syslog_log, :debug end end diff --git a/spec/ruby/library/syslog/emerg_spec.rb b/spec/ruby/library/syslog/emerg_spec.rb index 86938ce8897c59..2ab4d60291cb20 100644 --- a/spec/ruby/library/syslog/emerg_spec.rb +++ b/spec/ruby/library/syslog/emerg_spec.rb @@ -1,19 +1,16 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.emerg" do - # Some way needs do be found to prevent this spec - # from causing output on all open terminals. If this - # is not possible, this spec may need a special guard - # that only runs when requested. - quarantine! do - it_behaves_like :syslog_log, :emerg - end + describe "Syslog.emerg" do + # Some way needs do be found to prevent this spec + # from causing output on all open terminals. If this + # is not possible, this spec may need a special guard + # that only runs when requested. + quarantine! do + it_behaves_like :syslog_log, :emerg end end end diff --git a/spec/ruby/library/syslog/err_spec.rb b/spec/ruby/library/syslog/err_spec.rb index a7b39ea5c0abe7..43e876ed3782a6 100644 --- a/spec/ruby/library/syslog/err_spec.rb +++ b/spec/ruby/library/syslog/err_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.err" do - it_behaves_like :syslog_log, :err - end + describe "Syslog.err" do + it_behaves_like :syslog_log, :err end end diff --git a/spec/ruby/library/syslog/facility_spec.rb b/spec/ruby/library/syslog/facility_spec.rb index 1129dd9ee3e28a..550ca70b112cf9 100644 --- a/spec/ruby/library/syslog/facility_spec.rb +++ b/spec/ruby/library/syslog/facility_spec.rb @@ -1,50 +1,47 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - platform_is_not :windows do - require 'syslog' - - describe "Syslog.facility" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end - - after :each do - Syslog.opened?.should be_false - end - - it "returns the logging facility" do - Syslog.open("rubyspec", 3, Syslog::LOG_MAIL) - Syslog.facility.should == Syslog::LOG_MAIL - Syslog.close - end - - it "returns nil if the log is closed" do - Syslog.opened?.should be_false - Syslog.facility.should == nil - end - - it "defaults to LOG_USER" do - Syslog.open - Syslog.facility.should == Syslog::LOG_USER - Syslog.close - end - - it "resets after each open call" do - Syslog.open - Syslog.facility.should == Syslog::LOG_USER - - Syslog.open!("rubyspec", 3, Syslog::LOG_MAIL) - Syslog.facility.should == Syslog::LOG_MAIL - Syslog.close - - Syslog.open - Syslog.facility.should == Syslog::LOG_USER - Syslog.close - end +platform_is_not :windows do + require 'syslog' + + describe "Syslog.facility" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the logging facility" do + Syslog.open("rubyspec", 3, Syslog::LOG_MAIL) + Syslog.facility.should == Syslog::LOG_MAIL + Syslog.close + end + + it "returns nil if the log is closed" do + Syslog.opened?.should be_false + Syslog.facility.should == nil + end + + it "defaults to LOG_USER" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close + end + + it "resets after each open call" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + + Syslog.open!("rubyspec", 3, Syslog::LOG_MAIL) + Syslog.facility.should == Syslog::LOG_MAIL + Syslog.close + + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close end end end diff --git a/spec/ruby/library/syslog/ident_spec.rb b/spec/ruby/library/syslog/ident_spec.rb index 524e56037389df..3b083271406a68 100644 --- a/spec/ruby/library/syslog/ident_spec.rb +++ b/spec/ruby/library/syslog/ident_spec.rb @@ -1,37 +1,34 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' + describe "Syslog.ident" do + platform_is_not :windows do - describe "Syslog.ident" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "returns the logging identity" do - Syslog.open("rubyspec") - Syslog.ident.should == "rubyspec" - Syslog.close - end + it "returns the logging identity" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + end - it "returns nil if the log is closed" do - Syslog.should_not.opened? - Syslog.ident.should == nil - end + it "returns nil if the log is closed" do + Syslog.should_not.opened? + Syslog.ident.should == nil + end - it "defaults to $0" do - Syslog.open - Syslog.ident.should == $0 - Syslog.close - end + it "defaults to $0" do + Syslog.open + Syslog.ident.should == $0 + Syslog.close end end end diff --git a/spec/ruby/library/syslog/info_spec.rb b/spec/ruby/library/syslog/info_spec.rb index 03bb6f6003b73d..f2d535299c58a2 100644 --- a/spec/ruby/library/syslog/info_spec.rb +++ b/spec/ruby/library/syslog/info_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.info" do - it_behaves_like :syslog_log, :info - end + describe "Syslog.info" do + it_behaves_like :syslog_log, :info end end diff --git a/spec/ruby/library/syslog/inspect_spec.rb b/spec/ruby/library/syslog/inspect_spec.rb index 5e1e09e86fccb5..f45231f8e31def 100644 --- a/spec/ruby/library/syslog/inspect_spec.rb +++ b/spec/ruby/library/syslog/inspect_spec.rb @@ -1,41 +1,38 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' + describe "Syslog.inspect" do + platform_is_not :windows do - describe "Syslog.inspect" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "returns a string a closed log" do - Syslog.inspect.should =~ /opened=false/ - end + it "returns a string a closed log" do + Syslog.inspect.should =~ /opened=false/ + end - it "returns a string for an opened log" do - Syslog.open - Syslog.inspect.should =~ /opened=true.*/ - Syslog.close - end + it "returns a string for an opened log" do + Syslog.open + Syslog.inspect.should =~ /opened=true.*/ + Syslog.close + end - it "includes the ident, options, facility and mask" do - Syslog.open("rubyspec", Syslog::LOG_PID, Syslog::LOG_USER) - inspect_str = Syslog.inspect.split ", " - inspect_str[0].should =~ /opened=true/ - inspect_str[1].should == "ident=\"rubyspec\"" - inspect_str[2].should == "options=#{Syslog::LOG_PID}" - inspect_str[3].should == "facility=#{Syslog::LOG_USER}" - inspect_str[4].should == "mask=255>" - Syslog.close - end + it "includes the ident, options, facility and mask" do + Syslog.open("rubyspec", Syslog::LOG_PID, Syslog::LOG_USER) + inspect_str = Syslog.inspect.split ", " + inspect_str[0].should =~ /opened=true/ + inspect_str[1].should == "ident=\"rubyspec\"" + inspect_str[2].should == "options=#{Syslog::LOG_PID}" + inspect_str[3].should == "facility=#{Syslog::LOG_USER}" + inspect_str[4].should == "mask=255>" + Syslog.close end end end diff --git a/spec/ruby/library/syslog/instance_spec.rb b/spec/ruby/library/syslog/instance_spec.rb index b7a7d122f0990c..891296c52d1a0a 100644 --- a/spec/ruby/library/syslog/instance_spec.rb +++ b/spec/ruby/library/syslog/instance_spec.rb @@ -1,15 +1,12 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' - - describe "Syslog.instance" do - platform_is_not :windows do - it "returns the module" do - Syslog.instance.should == Syslog - end + describe "Syslog.instance" do + platform_is_not :windows do + it "returns the module" do + Syslog.instance.should == Syslog end end end diff --git a/spec/ruby/library/syslog/log_spec.rb b/spec/ruby/library/syslog/log_spec.rb index 749d825c104afa..8589fb1f7332ce 100644 --- a/spec/ruby/library/syslog/log_spec.rb +++ b/spec/ruby/library/syslog/log_spec.rb @@ -1,58 +1,55 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' + describe "Syslog.log" do + platform_is_not :windows, :darwin, :solaris, :aix, :android do - describe "Syslog.log" do - platform_is_not :windows, :darwin, :solaris, :aix, :android do - - before :each do - Syslog.opened?.should be_false - end - - after :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - it "receives a priority as first argument" do - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| - s.log(Syslog::LOG_ALERT, "Hello") - s.log(Syslog::LOG_CRIT, "World") - end - }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\nrubyspec(?::| \d+ - -) World\n\z/, $stderr) - end + after :each do + Syslog.opened?.should be_false + end - it "accepts undefined priorities" do - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| - s.log(1337, "Hello") - end - # use a regex since it'll output unknown facility/priority messages - }.should output_to_fd(/rubyspec(?::| \d+ - -) Hello\n\z/, $stderr) - end + it "receives a priority as first argument" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(Syslog::LOG_ALERT, "Hello") + s.log(Syslog::LOG_CRIT, "World") + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\nrubyspec(?::| \d+ - -) World\n\z/, $stderr) + end - it "fails with TypeError on nil log messages" do - Syslog.open do |s| - -> { s.log(1, nil) }.should raise_error(TypeError) + it "accepts undefined priorities" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(1337, "Hello") end - end + # use a regex since it'll output unknown facility/priority messages + }.should output_to_fd(/rubyspec(?::| \d+ - -) Hello\n\z/, $stderr) + end - it "fails if the log is closed" do - -> { - Syslog.log(Syslog::LOG_ALERT, "test") - }.should raise_error(RuntimeError) + it "fails with TypeError on nil log messages" do + Syslog.open do |s| + -> { s.log(1, nil) }.should raise_error(TypeError) end + end - it "accepts printf parameters" do - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| - s.log(Syslog::LOG_ALERT, "%s x %d", "chunky bacon", 2) - end - }.should output_to_fd(/rubyspec(?::| \d+ - -) chunky bacon x 2\n\z/, $stderr) - end + it "fails if the log is closed" do + -> { + Syslog.log(Syslog::LOG_ALERT, "test") + }.should raise_error(RuntimeError) + end + + it "accepts printf parameters" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do |s| + s.log(Syslog::LOG_ALERT, "%s x %d", "chunky bacon", 2) + end + }.should output_to_fd(/rubyspec(?::| \d+ - -) chunky bacon x 2\n\z/, $stderr) end end end diff --git a/spec/ruby/library/syslog/mask_spec.rb b/spec/ruby/library/syslog/mask_spec.rb index 05c64ceec89c05..b3f1250b24ac9d 100644 --- a/spec/ruby/library/syslog/mask_spec.rb +++ b/spec/ruby/library/syslog/mask_spec.rb @@ -1,114 +1,111 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' + describe "Syslog.mask" do + platform_is_not :windows do - describe "Syslog.mask" do - platform_is_not :windows do + before :each do + Syslog.opened?.should be_false + end - before :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + # make sure we return the mask to the default value + Syslog.open { |s| s.mask = 255 } + end - after :each do - Syslog.opened?.should be_false - # make sure we return the mask to the default value - Syslog.open { |s| s.mask = 255 } + it "returns the log priority mask" do + Syslog.open("rubyspec") do + Syslog.mask.should == 255 + Syslog.mask = 3 + Syslog.mask.should == 3 + Syslog.mask = 255 end + end - it "returns the log priority mask" do - Syslog.open("rubyspec") do - Syslog.mask.should == 255 - Syslog.mask = 3 - Syslog.mask.should == 3 - Syslog.mask = 255 - end + it "defaults to 255" do + Syslog.open do |s| + s.mask.should == 255 end + end - it "defaults to 255" do - Syslog.open do |s| - s.mask.should == 255 - end - end + it "returns nil if the log is closed" do + Syslog.should_not.opened? + Syslog.mask.should == nil + end - it "returns nil if the log is closed" do - Syslog.should_not.opened? - Syslog.mask.should == nil - end + platform_is :darwin do + it "resets if the log is reopened" do + Syslog.open + Syslog.mask.should == 255 + Syslog.mask = 64 - platform_is :darwin do - it "resets if the log is reopened" do - Syslog.open + Syslog.reopen("rubyspec") do Syslog.mask.should == 255 - Syslog.mask = 64 - - Syslog.reopen("rubyspec") do - Syslog.mask.should == 255 - end + end - Syslog.open do - Syslog.mask.should == 255 - end + Syslog.open do + Syslog.mask.should == 255 end end + end - platform_is_not :darwin do - it "persists if the log is reopened" do - Syslog.open - Syslog.mask.should == 255 - Syslog.mask = 64 + platform_is_not :darwin do + it "persists if the log is reopened" do + Syslog.open + Syslog.mask.should == 255 + Syslog.mask = 64 - Syslog.reopen("rubyspec") do - Syslog.mask.should == 64 - end + Syslog.reopen("rubyspec") do + Syslog.mask.should == 64 + end - Syslog.open do - Syslog.mask.should == 64 - end + Syslog.open do + Syslog.mask.should == 64 end end end end + end - describe "Syslog.mask=" do - platform_is_not :windows do + describe "Syslog.mask=" do + platform_is_not :windows do - before :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - # make sure we return the mask to the default value - Syslog.open { |s| s.mask = 255 } - end + after :each do + Syslog.opened?.should be_false + # make sure we return the mask to the default value + Syslog.open { |s| s.mask = 255 } + end - it "sets the log priority mask" do - Syslog.open do - Syslog.mask = 64 - Syslog.mask.should == 64 - end + it "sets the log priority mask" do + Syslog.open do + Syslog.mask = 64 + Syslog.mask.should == 64 end + end - it "raises an error if the log is closed" do - -> { Syslog.mask = 1337 }.should raise_error(RuntimeError) - end + it "raises an error if the log is closed" do + -> { Syslog.mask = 1337 }.should raise_error(RuntimeError) + end - it "only accepts numbers" do - Syslog.open do + it "only accepts numbers" do + Syslog.open do - Syslog.mask = 1337 - Syslog.mask.should == 1337 + Syslog.mask = 1337 + Syslog.mask.should == 1337 - Syslog.mask = 3.1416 - Syslog.mask.should == 3 + Syslog.mask = 3.1416 + Syslog.mask.should == 3 - -> { Syslog.mask = "oh hai" }.should raise_error(TypeError) - -> { Syslog.mask = "43" }.should raise_error(TypeError) + -> { Syslog.mask = "oh hai" }.should raise_error(TypeError) + -> { Syslog.mask = "43" }.should raise_error(TypeError) - end end end end diff --git a/spec/ruby/library/syslog/notice_spec.rb b/spec/ruby/library/syslog/notice_spec.rb index 41c175cb49f702..a2134e0140f15b 100644 --- a/spec/ruby/library/syslog/notice_spec.rb +++ b/spec/ruby/library/syslog/notice_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.notice" do - it_behaves_like :syslog_log, :notice - end + describe "Syslog.notice" do + it_behaves_like :syslog_log, :notice end end diff --git a/spec/ruby/library/syslog/open_spec.rb b/spec/ruby/library/syslog/open_spec.rb index 9cd65cda4a107f..543f5d418b3c09 100644 --- a/spec/ruby/library/syslog/open_spec.rb +++ b/spec/ruby/library/syslog/open_spec.rb @@ -1,95 +1,92 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/reopen' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/reopen' - require 'syslog' + describe "Syslog.open" do + platform_is_not :windows do - describe "Syslog.open" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "returns the module" do - Syslog.open.should == Syslog - Syslog.close - Syslog.open("Test", 5, 9).should == Syslog - Syslog.close - end + it "returns the module" do + Syslog.open.should == Syslog + Syslog.close + Syslog.open("Test", 5, 9).should == Syslog + Syslog.close + end - it "receives an identity as first argument" do - Syslog.open("rubyspec") - Syslog.ident.should == "rubyspec" - Syslog.close - end + it "receives an identity as first argument" do + Syslog.open("rubyspec") + Syslog.ident.should == "rubyspec" + Syslog.close + end - it "defaults the identity to $0" do - Syslog.open - Syslog.ident.should == $0 - Syslog.close - end + it "defaults the identity to $0" do + Syslog.open + Syslog.ident.should == $0 + Syslog.close + end - it "receives the logging options as second argument" do - Syslog.open("rubyspec", Syslog::LOG_PID) - Syslog.options.should == Syslog::LOG_PID - Syslog.close - end + it "receives the logging options as second argument" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + end - it "defaults the logging options to LOG_PID | LOG_CONS" do - Syslog.open - Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS - Syslog.close - end + it "defaults the logging options to LOG_PID | LOG_CONS" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close + end - it "receives a facility as third argument" do - Syslog.open("rubyspec", Syslog::LOG_PID, 0) - Syslog.facility.should == 0 - Syslog.close - end + it "receives a facility as third argument" do + Syslog.open("rubyspec", Syslog::LOG_PID, 0) + Syslog.facility.should == 0 + Syslog.close + end - it "defaults the facility to LOG_USER" do - Syslog.open - Syslog.facility.should == Syslog::LOG_USER - Syslog.close - end + it "defaults the facility to LOG_USER" do + Syslog.open + Syslog.facility.should == Syslog::LOG_USER + Syslog.close + end - it "receives a block and calls it with the module" do - Syslog.open("rubyspec", 3, 8) do |s| - s.should == Syslog - s.ident.should == "rubyspec" - s.options.should == 3 - s.facility.should == Syslog::LOG_USER - end + it "receives a block and calls it with the module" do + Syslog.open("rubyspec", 3, 8) do |s| + s.should == Syslog + s.ident.should == "rubyspec" + s.options.should == 3 + s.facility.should == Syslog::LOG_USER end + end - it "closes the log if after it receives a block" do - Syslog.open{ } - Syslog.opened?.should be_false - end + it "closes the log if after it receives a block" do + Syslog.open{ } + Syslog.opened?.should be_false + end - it "raises an error if the log is opened" do + it "raises an error if the log is opened" do + Syslog.open + -> { Syslog.open - -> { - Syslog.open - }.should raise_error(RuntimeError, /syslog already open/) - -> { - Syslog.close - Syslog.open - }.should_not raise_error + }.should raise_error(RuntimeError, /syslog already open/) + -> { Syslog.close - end + Syslog.open + }.should_not raise_error + Syslog.close end end + end - describe "Syslog.open!" do - it_behaves_like :syslog_reopen, :open! - end + describe "Syslog.open!" do + it_behaves_like :syslog_reopen, :open! end end diff --git a/spec/ruby/library/syslog/opened_spec.rb b/spec/ruby/library/syslog/opened_spec.rb index ee7a884a50fce0..94432e65a440d7 100644 --- a/spec/ruby/library/syslog/opened_spec.rb +++ b/spec/ruby/library/syslog/opened_spec.rb @@ -1,41 +1,38 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require 'syslog' - platform_is_not :windows do - require 'syslog' + describe "Syslog.opened?" do + platform_is_not :windows do - describe "Syslog.opened?" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "returns true if the log is opened" do - Syslog.open - Syslog.opened?.should be_true - Syslog.close - end + it "returns true if the log is opened" do + Syslog.open + Syslog.opened?.should be_true + Syslog.close + end - it "returns false otherwise" do - Syslog.opened?.should be_false - Syslog.open - Syslog.close - Syslog.opened?.should be_false - end + it "returns false otherwise" do + Syslog.opened?.should be_false + Syslog.open + Syslog.close + Syslog.opened?.should be_false + end - it "works inside a block" do - Syslog.open do |s| - s.opened?.should be_true - Syslog.opened?.should be_true - end - Syslog.opened?.should be_false + it "works inside a block" do + Syslog.open do |s| + s.opened?.should be_true + Syslog.opened?.should be_true end + Syslog.opened?.should be_false end end end diff --git a/spec/ruby/library/syslog/options_spec.rb b/spec/ruby/library/syslog/options_spec.rb index 814f7daee60a68..83ba43503ec674 100644 --- a/spec/ruby/library/syslog/options_spec.rb +++ b/spec/ruby/library/syslog/options_spec.rb @@ -1,50 +1,47 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - platform_is_not :windows do - require 'syslog' - - describe "Syslog.options" do - platform_is_not :windows do - - before :each do - Syslog.opened?.should be_false - end - - after :each do - Syslog.opened?.should be_false - end - - it "returns the logging options" do - Syslog.open("rubyspec", Syslog::LOG_PID) - Syslog.options.should == Syslog::LOG_PID - Syslog.close - end - - it "returns nil when the log is closed" do - Syslog.opened?.should be_false - Syslog.options.should == nil - end - - it "defaults to LOG_PID | LOG_CONS" do - Syslog.open - Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS - Syslog.close - end - - it "resets after each open call" do - Syslog.open - Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS - - Syslog.open!("rubyspec", Syslog::LOG_PID) - Syslog.options.should == Syslog::LOG_PID - Syslog.close - - Syslog.open - Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS - Syslog.close - end +platform_is_not :windows do + require 'syslog' + + describe "Syslog.options" do + platform_is_not :windows do + + before :each do + Syslog.opened?.should be_false + end + + after :each do + Syslog.opened?.should be_false + end + + it "returns the logging options" do + Syslog.open("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + end + + it "returns nil when the log is closed" do + Syslog.opened?.should be_false + Syslog.options.should == nil + end + + it "defaults to LOG_PID | LOG_CONS" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close + end + + it "resets after each open call" do + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + + Syslog.open!("rubyspec", Syslog::LOG_PID) + Syslog.options.should == Syslog::LOG_PID + Syslog.close + + Syslog.open + Syslog.options.should == Syslog::LOG_PID | Syslog::LOG_CONS + Syslog.close end end end diff --git a/spec/ruby/library/syslog/reopen_spec.rb b/spec/ruby/library/syslog/reopen_spec.rb index 47861bc1c0df39..a78529fa1fe083 100644 --- a/spec/ruby/library/syslog/reopen_spec.rb +++ b/spec/ruby/library/syslog/reopen_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/reopen' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/reopen' - require 'syslog' - - describe "Syslog.reopen" do - it_behaves_like :syslog_reopen, :reopen - end + describe "Syslog.reopen" do + it_behaves_like :syslog_reopen, :reopen end end diff --git a/spec/ruby/library/syslog/shared/log.rb b/spec/ruby/library/syslog/shared/log.rb index d6daf3cc67c9a7..12e4ea8366a2e3 100644 --- a/spec/ruby/library/syslog/shared/log.rb +++ b/spec/ruby/library/syslog/shared/log.rb @@ -1,41 +1,39 @@ -ruby_version_is ""..."3.4" do - describe :syslog_log, shared: true do - platform_is_not :windows, :darwin, :solaris, :aix, :android do - before :each do - Syslog.opened?.should be_false - end +describe :syslog_log, shared: true do + platform_is_not :windows, :darwin, :solaris, :aix, :android do + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "logs a message" do - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do - Syslog.send(@method, "Hello") - end - }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\n\z/, $stderr) - end + it "logs a message" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello") + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello\n\z/, $stderr) + end - it "accepts sprintf arguments" do - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do - Syslog.send(@method, "Hello %s", "world") - Syslog.send(@method, "%d dogs", 2) - end - }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello world\nrubyspec(?::| \d+ - -) 2 dogs\n\z/, $stderr) - end + it "accepts sprintf arguments" do + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello %s", "world") + Syslog.send(@method, "%d dogs", 2) + end + }.should output_to_fd(/\Arubyspec(?::| \d+ - -) Hello world\nrubyspec(?::| \d+ - -) 2 dogs\n\z/, $stderr) + end - it "works as an alias for Syslog.log" do - level = Syslog.const_get "LOG_#{@method.to_s.upcase}" - -> { - Syslog.open("rubyspec", Syslog::LOG_PERROR) do - Syslog.send(@method, "Hello") - Syslog.log(level, "Hello") - end - # make sure the same thing is written to $stderr. - }.should output_to_fd(/\A(?:rubyspec(?::| \d+ - -) Hello\n){2}\z/, $stderr) - end + it "works as an alias for Syslog.log" do + level = Syslog.const_get "LOG_#{@method.to_s.upcase}" + -> { + Syslog.open("rubyspec", Syslog::LOG_PERROR) do + Syslog.send(@method, "Hello") + Syslog.log(level, "Hello") + end + # make sure the same thing is written to $stderr. + }.should output_to_fd(/\A(?:rubyspec(?::| \d+ - -) Hello\n){2}\z/, $stderr) end end end diff --git a/spec/ruby/library/syslog/shared/reopen.rb b/spec/ruby/library/syslog/shared/reopen.rb index 935349010bed7f..621437a01d8528 100644 --- a/spec/ruby/library/syslog/shared/reopen.rb +++ b/spec/ruby/library/syslog/shared/reopen.rb @@ -1,42 +1,40 @@ -ruby_version_is ""..."3.4" do - describe :syslog_reopen, shared: true do - platform_is_not :windows do - before :each do - Syslog.opened?.should be_false - end +describe :syslog_reopen, shared: true do + platform_is_not :windows do + before :each do + Syslog.opened?.should be_false + end - after :each do - Syslog.opened?.should be_false - end + after :each do + Syslog.opened?.should be_false + end - it "reopens the log" do - Syslog.open - -> { Syslog.send(@method)}.should_not raise_error - Syslog.opened?.should be_true - Syslog.close - end + it "reopens the log" do + Syslog.open + -> { Syslog.send(@method)}.should_not raise_error + Syslog.opened?.should be_true + Syslog.close + end - it "fails with RuntimeError if the log is closed" do - -> { Syslog.send(@method)}.should raise_error(RuntimeError) - end + it "fails with RuntimeError if the log is closed" do + -> { Syslog.send(@method)}.should raise_error(RuntimeError) + end - it "receives the same parameters as Syslog.open" do - Syslog.open - Syslog.send(@method, "rubyspec", 3, 8) do |s| - s.should == Syslog - s.ident.should == "rubyspec" - s.options.should == 3 - s.facility.should == Syslog::LOG_USER - s.opened?.should be_true - end - Syslog.opened?.should be_false + it "receives the same parameters as Syslog.open" do + Syslog.open + Syslog.send(@method, "rubyspec", 3, 8) do |s| + s.should == Syslog + s.ident.should == "rubyspec" + s.options.should == 3 + s.facility.should == Syslog::LOG_USER + s.opened?.should be_true end + Syslog.opened?.should be_false + end - it "returns the module" do - Syslog.open - Syslog.send(@method).should == Syslog - Syslog.close - end + it "returns the module" do + Syslog.open + Syslog.send(@method).should == Syslog + Syslog.close end end end diff --git a/spec/ruby/library/syslog/warning_spec.rb b/spec/ruby/library/syslog/warning_spec.rb index cf0f7d0dc21484..eeca603136211f 100644 --- a/spec/ruby/library/syslog/warning_spec.rb +++ b/spec/ruby/library/syslog/warning_spec.rb @@ -1,13 +1,10 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do +platform_is_not :windows do + require_relative 'shared/log' + require 'syslog' - platform_is_not :windows do - require_relative 'shared/log' - require 'syslog' - - describe "Syslog.warning" do - it_behaves_like :syslog_log, :warning - end + describe "Syslog.warning" do + it_behaves_like :syslog_log, :warning end end diff --git a/spec/ruby/optional/capi/ext/util_spec.c b/spec/ruby/optional/capi/ext/util_spec.c index 95ba71ea9dc11f..b5bde420d212dc 100644 --- a/spec/ruby/optional/capi/ext/util_spec.c +++ b/spec/ruby/optional/capi/ext/util_spec.c @@ -62,22 +62,17 @@ static VALUE util_spec_rb_get_kwargs(VALUE self, VALUE keyword_hash, VALUE keys, int len = RARRAY_LENINT(keys); int values_len = req + (opt < 0 ? -1 - opt : opt); - int i = 0; - ID *ids = (ID*) malloc(sizeof(VALUE) * len); - VALUE *results = (VALUE*) malloc(sizeof(VALUE) * values_len); - int extracted = 0; - VALUE ary = Qundef; + ID *ids = (ID *)alloca(sizeof(VALUE) * len); + VALUE *results = (VALUE *)alloca(sizeof(VALUE) * values_len); - for (i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { ids[i] = SYM2ID(rb_ary_entry(keys, i)); } - extracted = rb_get_kwargs(keyword_hash, ids, req, opt, results); - ary = rb_ary_new_from_values(extracted, results); - free(results); - free(ids); - return ary; + int extracted = rb_get_kwargs(keyword_hash, ids, req, opt, results); + + return rb_ary_new_from_values(extracted, results); } static VALUE util_spec_rb_long2int(VALUE self, VALUE n) { diff --git a/spec/ruby/shared/rational/coerce.rb b/spec/ruby/shared/rational/coerce.rb index 38925721ed31fc..ccc8901ba0a76c 100644 --- a/spec/ruby/shared/rational/coerce.rb +++ b/spec/ruby/shared/rational/coerce.rb @@ -1,5 +1,7 @@ require_relative '../../spec_helper' +require 'bigdecimal' + describe :rational_coerce, shared: true do it "returns the passed argument, self as Float, when given a Float" do result = Rational(3, 4).coerce(1.0) @@ -24,12 +26,9 @@ Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)] end - ruby_version_is ""..."3.4" do - require 'bigdecimal' - it "raises an error when passed a BigDecimal" do - -> { - Rational(500, 3).coerce(BigDecimal('166.666666666')) - }.should raise_error(TypeError, /BigDecimal can't be coerced into Rational/) - end + it "raises an error when passed a BigDecimal" do + -> { + Rational(500, 3).coerce(BigDecimal('166.666666666')) + }.should raise_error(TypeError, /BigDecimal can't be coerced into Rational/) end end From d15301d4827cb0e14dedf6456ee64ab0a36622f9 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 11:05:51 +0100 Subject: [PATCH 134/142] Exclude a problematic spec when run in CRuby via make test-spec until fixed --- spec/ruby/library/drb/start_service_spec.rb | 47 ++++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/spec/ruby/library/drb/start_service_spec.rb b/spec/ruby/library/drb/start_service_spec.rb index 016c8b2cffbb73..57a8cf6e15d5af 100644 --- a/spec/ruby/library/drb/start_service_spec.rb +++ b/spec/ruby/library/drb/start_service_spec.rb @@ -1,28 +1,33 @@ require_relative '../../spec_helper' -require_relative 'fixtures/test_server' -require 'drb' -describe "DRb.start_service" do - before :each do - @server = DRb.start_service("druby://localhost:0", TestServer.new) - end +# This does not work yet when run in CRuby via make test-spec: +# Gem::MissingSpecError: Could not find 'ruby2_keywords' (>= 0) among 28 total gem(s) +guard_not -> { MSpecScript.instance_variable_defined?(:@testing_ruby) } do + require_relative 'fixtures/test_server' + require 'drb' - after :each do - DRb.stop_service if @server - end + describe "DRb.start_service" do + before :each do + @server = DRb.start_service("druby://localhost:0", TestServer.new) + end - it "runs a basic remote call" do - DRb.current_server.should == @server - obj = DRbObject.new(nil, @server.uri) - obj.add(1,2,3).should == 6 - end + after :each do + DRb.stop_service if @server + end + + it "runs a basic remote call" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add(1,2,3).should == 6 + end - it "runs a basic remote call passing a block" do - DRb.current_server.should == @server - obj = DRbObject.new(nil, @server.uri) - obj.add_yield(2) do |i| - i.should == 2 - i+1 - end.should == 4 + it "runs a basic remote call passing a block" do + DRb.current_server.should == @server + obj = DRbObject.new(nil, @server.uri) + obj.add_yield(2) do |i| + i.should == 2 + i+1 + end.should == 4 + end end end From ffe1a68bda208c776656f47a8c743cc3e6b7aac6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 11:33:08 +0100 Subject: [PATCH 135/142] Skip spec failing on i686 --- spec/ruby/library/bigdecimal/sqrt_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/ruby/library/bigdecimal/sqrt_spec.rb b/spec/ruby/library/bigdecimal/sqrt_spec.rb index d149003b9f5a96..8fd1ec0f39fac2 100644 --- a/spec/ruby/library/bigdecimal/sqrt_spec.rb +++ b/spec/ruby/library/bigdecimal/sqrt_spec.rb @@ -36,8 +36,10 @@ BigDecimal('121').sqrt(5).should be_close(11, 0.00001) end - it "returns square root of 0.9E-99999 with desired precision" do - @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i + platform_is_not wordsize: 32 do # fails on i686 + it "returns square root of 0.9E-99999 with desired precision" do + @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i + end end it "raises ArgumentError when no argument is given" do From 39788e5888c8117b07a4c06390d08ce032d9a42b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 11:33:19 +0100 Subject: [PATCH 136/142] Try prepare-gems instead of extract-gems * `make install` uses prepare-gems. --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 31a74d3ead6788..4ad47ca86c9ade 100644 --- a/common.mk +++ b/common.mk @@ -983,7 +983,7 @@ $(RBCONFIG): $(tooldir)/mkconfig.rb config.status $(srcdir)/version.h $(srcdir)/ test-rubyspec: test-spec yes-test-rubyspec: yes-test-spec -yes-test-spec-precheck: yes-test-all-precheck yes-fake extract-gems +yes-test-spec-precheck: yes-test-all-precheck yes-fake prepare-gems test-spec: $(TEST_RUNNABLE)-test-spec yes-test-spec: yes-test-spec-precheck From 06995eb45b86645a945b4429c6d85597a21b40a7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Mon, 12 Feb 2024 20:28:50 +0900 Subject: [PATCH 137/142] [ruby/irb] Fix exit! command warning and method behavior (https://github.com/ruby/irb/pull/868) * Fix exit! command warning and method behavior * Remove arg(0) from Kernel.exit and Kernel.exit! https://github.com/ruby/irb/commit/372bc59bf5 --- lib/irb.rb | 2 +- lib/irb/cmd/force_exit.rb | 2 +- lib/irb/workspace.rb | 6 +++--- test/irb/cmd/test_force_exit.rb | 12 ++++++++++++ test/irb/test_cmd.rb | 4 +--- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 3830867e6aee8c..218920bc4138e6 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -989,7 +989,7 @@ def run(conf = IRB.conf) conf[:AT_EXIT].each{|hook| hook.call} context.io.save_history if save_history - Kernel.exit(0) if forced_exit + Kernel.exit if forced_exit end end diff --git a/lib/irb/cmd/force_exit.rb b/lib/irb/cmd/force_exit.rb index 2b9f296865c0ad..7e9b308de0539a 100644 --- a/lib/irb/cmd/force_exit.rb +++ b/lib/irb/cmd/force_exit.rb @@ -13,7 +13,7 @@ class ForceExit < Nop def execute(*) throw :IRB_EXIT, true rescue UncaughtThrowError - Kernel.exit(0) + Kernel.exit! end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 2bf3d5e0f11f1a..aaf2f335e227b4 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -90,11 +90,11 @@ def initialize(*main) IRB.conf[:__MAIN__] = @main @main.singleton_class.class_eval do private - define_method(:exit) do |*a, &b| - # Do nothing, will be overridden - end define_method(:binding, Kernel.instance_method(:binding)) define_method(:local_variables, Kernel.instance_method(:local_variables)) + # Define empty method to avoid delegator warning, will be overridden. + define_method(:exit) {|*a, &b| } + define_method(:exit!) {|*a, &b| } end @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location) end diff --git a/test/irb/cmd/test_force_exit.rb b/test/irb/cmd/test_force_exit.rb index 191a7868721e3e..9e86c644d6d8a4 100644 --- a/test/irb/cmd/test_force_exit.rb +++ b/test/irb/cmd/test_force_exit.rb @@ -47,5 +47,17 @@ def foo assert_match(/irb\(main\):001> 123/, output) end + + def test_forced_exit_out_of_irb_session + write_ruby <<~'ruby' + at_exit { puts 'un' + 'reachable' } + binding.irb + exit! # this will call exit! method overrided by command + ruby + output = run_ruby_file do + type "exit" + end + assert_not_include(output, 'unreachable') + end end end diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 7d7353281e2c69..349d2c04571e18 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -58,9 +58,7 @@ def test_calling_command_on_a_frozen_main "irb_info", main: main ) - # Because the main object is frozen, IRB would wrap a delegator around it - # Which's exit! method can't be overridden and would raise a warning - assert_match(/delegator does not forward private method #exit\!/, err) + assert_empty(err) assert_match(/RUBY_PLATFORM/, out) end end From d4a6c6521aa1a5208939a2cd981a13ca01a07d2a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 12 Feb 2024 14:00:58 +0100 Subject: [PATCH 138/142] Try `nmake install` before `nmake test-spec` --- .github/workflows/windows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0776857589c5a6..42d1f6b6335a5e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -184,6 +184,8 @@ jobs: - run: nmake test timeout-minutes: 5 + - run: nmake install + - run: nmake test-spec timeout-minutes: 10 From 190a55d27f82eff05ae5bfcac0cb2603ab75791a Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 9 Feb 2024 12:06:53 -0500 Subject: [PATCH 139/142] Drill newobj cache instead of ractor --- gc.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/gc.c b/gc.c index 124f2158ffde7c..220ae14d91f11b 100644 --- a/gc.c +++ b/gc.c @@ -2799,11 +2799,10 @@ size_pool_idx_for_size(size_t size) } static VALUE -newobj_alloc(rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx, bool vm_locked) +newobj_alloc(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx, bool vm_locked) { rb_size_pool_t *size_pool = &size_pools[size_pool_idx]; rb_heap_t *heap = SIZE_POOL_EDEN_HEAP(size_pool); - rb_ractor_newobj_cache_t *cache = &cr->newobj_cache; VALUE obj = ractor_cache_allocate_slot(objspace, cache, size_pool_idx); @@ -2812,7 +2811,7 @@ newobj_alloc(rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx, boo bool unlock_vm = false; if (!vm_locked) { - RB_VM_LOCK_ENTER_CR_LEV(cr, &lev); + RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev); vm_locked = true; unlock_vm = true; } @@ -2841,7 +2840,7 @@ newobj_alloc(rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx, boo } if (unlock_vm) { - RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev); + RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev); } } @@ -2856,15 +2855,15 @@ newobj_zero_slot(VALUE obj) memset((char *)obj + sizeof(struct RBasic), 0, rb_gc_obj_slot_size(obj) - sizeof(struct RBasic)); } -ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, int wb_protected, size_t size_pool_idx)); +ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t size_pool_idx)); static inline VALUE -newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, int wb_protected, size_t size_pool_idx) +newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, int wb_protected, size_t size_pool_idx) { VALUE obj; unsigned int lev; - RB_VM_LOCK_ENTER_CR_LEV(cr, &lev); + RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev); { if (UNLIKELY(during_gc || ruby_gc_stressful)) { if (during_gc) { @@ -2880,35 +2879,35 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t * } } - obj = newobj_alloc(objspace, cr, size_pool_idx, true); + obj = newobj_alloc(objspace, cache, size_pool_idx, true); newobj_init(klass, flags, wb_protected, objspace, obj); gc_event_hook_prep(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj, newobj_zero_slot(obj)); } - RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev); + RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev); return obj; } NOINLINE(static VALUE newobj_slowpath_wb_protected(VALUE klass, VALUE flags, - rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx)); + rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx)); NOINLINE(static VALUE newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, - rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx)); + rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx)); static VALUE -newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx) +newobj_slowpath_wb_protected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx) { - return newobj_slowpath(klass, flags, objspace, cr, TRUE, size_pool_idx); + return newobj_slowpath(klass, flags, objspace, cache, TRUE, size_pool_idx); } static VALUE -newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx) +newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx) { - return newobj_slowpath(klass, flags, objspace, cr, FALSE, size_pool_idx); + return newobj_slowpath(klass, flags, objspace, cache, FALSE, size_pool_idx); } static inline VALUE -newobj_of0(VALUE klass, VALUE flags, int wb_protected, rb_ractor_t *cr, size_t alloc_size) +newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected, size_t alloc_size) { VALUE obj; rb_objspace_t *objspace = &rb_objspace; @@ -2929,28 +2928,23 @@ newobj_of0(VALUE klass, VALUE flags, int wb_protected, rb_ractor_t *cr, size_t a flags |= (VALUE)size_pool_idx << SHAPE_FLAG_SHIFT; } + rb_ractor_newobj_cache_t *cache = &cr->newobj_cache; + if (!UNLIKELY(during_gc || ruby_gc_stressful || gc_event_newobj_hook_needed_p(objspace)) && wb_protected) { - obj = newobj_alloc(objspace, cr, size_pool_idx, false); + obj = newobj_alloc(objspace, cache, size_pool_idx, false); newobj_init(klass, flags, wb_protected, objspace, obj); } else { RB_DEBUG_COUNTER_INC(obj_newobj_slowpath); obj = wb_protected ? - newobj_slowpath_wb_protected(klass, flags, objspace, cr, size_pool_idx) : - newobj_slowpath_wb_unprotected(klass, flags, objspace, cr, size_pool_idx); + newobj_slowpath_wb_protected(klass, flags, objspace, cache, size_pool_idx) : + newobj_slowpath_wb_unprotected(klass, flags, objspace, cache, size_pool_idx); } - return obj; -} - -static inline VALUE -newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected, size_t alloc_size) -{ - VALUE obj = newobj_of0(klass, flags, wb_protected, cr, alloc_size); return newobj_fill(obj, v1, v2, v3); } From 739eec0456e50c6b83ff28d5887310102f5f1f2c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 13 Feb 2024 00:48:10 +0900 Subject: [PATCH 140/142] [DOC] `:stopdoc:` directive must be on its own line (#9916) --- yjit.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yjit.rb b/yjit.rb index 18b00c07650c84..8ceb2b23cc6813 100644 --- a/yjit.rb +++ b/yjit.rb @@ -234,7 +234,8 @@ def self.simulate_oom! # :nodoc: at_exit { print_and_dump_stats } end - class << self # :stopdoc: + class << self + # :stopdoc: private # Print stats and dump exit locations @@ -487,5 +488,7 @@ def format_number_pct(pad, number, total) formatted_pct = "%4.1f%%" % percentage "#{padded_count} (#{formatted_pct})" end + + # :startdoc: end end From 78deba1aa1d0902f610161655c50d73e8e2f7204 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 26 Jan 2024 15:13:46 -0500 Subject: [PATCH 141/142] [ruby/prism] Unary not name location https://github.com/ruby/prism/commit/78190d2999 --- prism/prism.c | 13 ++++++++----- test/prism/snapshots/seattlerb/defn_unary_not.txt | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 9df29a2de5a55d..533280c0b9d9a1 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2890,7 +2890,8 @@ pm_def_node_receiver_check(pm_parser_t *parser, const pm_node_t *node) { static pm_def_node_t * pm_def_node_create( pm_parser_t *parser, - const pm_token_t *name, + pm_constant_id_t name, + const pm_location_t *name_loc, pm_node_t *receiver, pm_parameters_node_t *parameters, pm_node_t *body, @@ -2920,8 +2921,8 @@ pm_def_node_create( .type = PM_DEF_NODE, .location = { .start = def_keyword->start, .end = end }, }, - .name = pm_parser_constant_id_token(parser, name), - .name_loc = PM_LOCATION_TOKEN_VALUE(name), + .name = name, + .name_loc = *name_loc, .receiver = receiver, .parameters = parameters, .body = body, @@ -15626,11 +15627,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b * methods to override the unary operators, we should ignore * the @ in the same way we do for symbols. */ - name.end = parse_operator_symbol_name(&name); + pm_constant_id_t name_id = pm_parser_constant_id_token(parser, &name); + pm_location_t name_loc = { .start = name.start, .end = parse_operator_symbol_name(&name) }; return (pm_node_t *) pm_def_node_create( parser, - &name, + name_id, + &name_loc, receiver, params, statements, diff --git a/test/prism/snapshots/seattlerb/defn_unary_not.txt b/test/prism/snapshots/seattlerb/defn_unary_not.txt index df2e3a87bb9026..a659aeb9c254bb 100644 --- a/test/prism/snapshots/seattlerb/defn_unary_not.txt +++ b/test/prism/snapshots/seattlerb/defn_unary_not.txt @@ -4,7 +4,7 @@ @ StatementsNode (location: (1,0)-(1,17)) └── body: (length: 1) └── @ DefNode (location: (1,0)-(1,17)) - ├── name: :! + ├── name: :"!@" ├── name_loc: (1,4)-(1,5) = "!" ├── receiver: ∅ ├── parameters: ∅ From 16b39072a56c253acdb35055e42a69631cf00d69 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Mon, 12 Feb 2024 15:47:33 +0000 Subject: [PATCH 142/142] [ruby/prism] Move Prism::RipperCompat to Prism::Translation::Ripper https://github.com/ruby/prism/commit/c0331abe4f --- lib/prism.rb | 1 - lib/prism/prism.gemspec | 2 +- lib/prism/ripper_compat.rb | 575 ----------------- lib/prism/translation.rb | 1 + lib/prism/translation/ripper.rb | 577 ++++++++++++++++++ .../{ripper_compat_test.rb => ripper_test.rb} | 8 +- 6 files changed, 583 insertions(+), 581 deletions(-) delete mode 100644 lib/prism/ripper_compat.rb create mode 100644 lib/prism/translation/ripper.rb rename test/prism/{ripper_compat_test.rb => ripper_test.rb} (95%) diff --git a/lib/prism.rb b/lib/prism.rb index 5d78b42c4dc041..8a2e7a61083168 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -22,7 +22,6 @@ module Prism autoload :LexRipper, "prism/lex_compat" autoload :MutationCompiler, "prism/mutation_compiler" autoload :NodeInspector, "prism/node_inspector" - autoload :RipperCompat, "prism/ripper_compat" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" autoload :Serialize, "prism/serialize" diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 0d0d548d35395a..3d2602d430660f 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -83,13 +83,13 @@ Gem::Specification.new do |spec| "lib/prism/parse_result/comments.rb", "lib/prism/parse_result/newlines.rb", "lib/prism/pattern.rb", - "lib/prism/ripper_compat.rb", "lib/prism/serialize.rb", "lib/prism/translation.rb", "lib/prism/translation/parser.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", "lib/prism/translation/parser/rubocop.rb", + "lib/prism/translation/ripper.rb", "lib/prism/visitor.rb", "src/diagnostic.c", "src/encoding.c", diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb deleted file mode 100644 index 6e10737e0d5bd6..00000000000000 --- a/lib/prism/ripper_compat.rb +++ /dev/null @@ -1,575 +0,0 @@ -# frozen_string_literal: true - -require "ripper" - -module Prism - # Note: This integration is not finished, and therefore still has many - # inconsistencies with Ripper. If you'd like to help out, pull requests would - # be greatly appreciated! - # - # This class is meant to provide a compatibility layer between prism and - # Ripper. It functions by parsing the entire tree first and then walking it - # and executing each of the Ripper callbacks as it goes. - # - # This class is going to necessarily be slower than the native Ripper API. It - # is meant as a stopgap until developers migrate to using prism. It is also - # meant as a test harness for the prism parser. - # - # To use this class, you treat `Prism::RipperCompat` effectively as you would - # treat the `Ripper` class. - class RipperCompat < Compiler - # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that - # returns the arrays of [type, *children]. - class SexpBuilder < RipperCompat - private - - Ripper::PARSER_EVENTS.each do |event| - define_method(:"on_#{event}") do |*args| - [event, *args] - end - end - - Ripper::SCANNER_EVENTS.each do |event| - define_method(:"on_#{event}") do |value| - [:"@#{event}", value, [lineno, column]] - end - end - end - - # This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that - # returns the same values as ::Ripper::SexpBuilder except with a couple of - # niceties that flatten linked lists into arrays. - class SexpBuilderPP < SexpBuilder - private - - def _dispatch_event_new # :nodoc: - [] - end - - def _dispatch_event_push(list, item) # :nodoc: - list << item - list - end - - Ripper::PARSER_EVENT_TABLE.each do |event, arity| - case event - when /_new\z/ - alias_method :"on_#{event}", :_dispatch_event_new if arity == 0 - when /_add\z/ - alias_method :"on_#{event}", :_dispatch_event_push - end - end - end - - # The source that is being parsed. - attr_reader :source - - # The current line number of the parser. - attr_reader :lineno - - # The current column number of the parser. - attr_reader :column - - # Create a new RipperCompat object with the given source. - def initialize(source) - @source = source - @result = nil - @lineno = nil - @column = nil - end - - ############################################################################ - # Public interface - ############################################################################ - - # True if the parser encountered an error during parsing. - def error? - result.failure? - end - - # Parse the source and return the result. - def parse - result.magic_comments.each do |magic_comment| - on_magic_comment(magic_comment.key, magic_comment.value) - end - - if error? - result.errors.each do |error| - on_parse_error(error.message) - end - - nil - else - result.value.accept(self) - end - end - - ############################################################################ - # Visitor methods - ############################################################################ - - # Visit an ArrayNode node. - def visit_array_node(node) - elements = visit_elements(node.elements) unless node.elements.empty? - bounds(node.location) - on_array(elements) - end - - # Visit a CallNode node. - # Ripper distinguishes between many different method-call - # nodes -- unary and binary operators, "command" calls with - # no parentheses, and call/fcall/vcall. - def visit_call_node(node) - return visit_aref_node(node) if node.name == :[] - return visit_aref_field_node(node) if node.name == :[]= - - if node.variable_call? - raise NotImplementedError unless node.receiver.nil? - - bounds(node.message_loc) - return on_vcall(on_ident(node.message)) - end - - if node.opening_loc.nil? - return visit_no_paren_call(node) - end - - # A non-operator method call with parentheses - - args = if node.arguments.nil? - on_arg_paren(nil) - else - on_arg_paren(on_args_add_block(visit_elements(node.arguments.arguments), false)) - end - - bounds(node.message_loc) - ident_val = on_ident(node.message) - - bounds(node.location) - args_call_val = on_method_add_arg(on_fcall(ident_val), args) - if node.block - block_val = visit(node.block) - - return on_method_add_block(args_call_val, block_val) - else - return args_call_val - end - end - - # Visit a LocalVariableWriteNode. - def visit_local_variable_write_node(node) - bounds(node.name_loc) - ident_val = on_ident(node.name.to_s) - on_assign(on_var_field(ident_val), visit(node.value)) - end - - # Visit a LocalVariableAndWriteNode. - def visit_local_variable_and_write_node(node) - visit_binary_op_assign(node) - end - - # Visit a LocalVariableOrWriteNode. - def visit_local_variable_or_write_node(node) - visit_binary_op_assign(node) - end - - # Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes. - def visit_local_variable_operator_write_node(node) - visit_binary_op_assign(node, operator: "#{node.operator}=") - end - - # Visit a LocalVariableReadNode. - def visit_local_variable_read_node(node) - bounds(node.location) - ident_val = on_ident(node.slice) - - on_var_ref(ident_val) - end - - # Visit a BlockNode. - def visit_block_node(node) - params_val = node.parameters.nil? ? nil : visit(node.parameters) - - body_val = node.body.nil? ? on_stmts_add(on_stmts_new, on_void_stmt) : visit(node.body) - - on_brace_block(params_val, body_val) - end - - # Visit a BlockParametersNode. - def visit_block_parameters_node(node) - on_block_var(visit(node.parameters), no_block_value) - end - - # Visit a ParametersNode. - # This will require expanding as we support more kinds of parameters. - def visit_parameters_node(node) - #on_params(required, optional, nil, nil, nil, nil, nil) - on_params(visit_all(node.requireds), nil, nil, nil, nil, nil, nil) - end - - # Visit a RequiredParameterNode. - def visit_required_parameter_node(node) - bounds(node.location) - on_ident(node.name.to_s) - end - - # Visit a BreakNode. - def visit_break_node(node) - return on_break(on_args_new) if node.arguments.nil? - - args_val = visit_elements(node.arguments.arguments) - on_break(on_args_add_block(args_val, false)) - end - - # Visit an AndNode. - def visit_and_node(node) - visit_binary_operator(node) - end - - # Visit an OrNode. - def visit_or_node(node) - visit_binary_operator(node) - end - - # Visit a TrueNode. - def visit_true_node(node) - bounds(node.location) - on_var_ref(on_kw("true")) - end - - # Visit a FalseNode. - def visit_false_node(node) - bounds(node.location) - on_var_ref(on_kw("false")) - end - - # Visit a FloatNode node. - def visit_float_node(node) - visit_number(node) { |text| on_float(text) } - end - - # Visit a ImaginaryNode node. - def visit_imaginary_node(node) - visit_number(node) { |text| on_imaginary(text) } - end - - # Visit an IntegerNode node. - def visit_integer_node(node) - visit_number(node) { |text| on_int(text) } - end - - # Visit a ParenthesesNode node. - def visit_parentheses_node(node) - body = - if node.body.nil? - on_stmts_add(on_stmts_new, on_void_stmt) - else - visit(node.body) - end - - bounds(node.location) - on_paren(body) - end - - # Visit a BeginNode node. - # This is not at all bulletproof against different structures of begin/rescue/else/ensure/end. - def visit_begin_node(node) - rescue_val = node.rescue_clause ? on_rescue(nil, nil, visit(node.rescue_clause), nil) : nil - ensure_val = node.ensure_clause ? on_ensure(visit(node.ensure_clause.statements)) : nil - on_begin(on_bodystmt(visit(node.statements), rescue_val, nil, ensure_val)) - end - - # Visit a RescueNode node. - def visit_rescue_node(node) - visit(node.statements) - end - - # Visit a ProgramNode node. - def visit_program_node(node) - statements = visit(node.statements) - bounds(node.location) - on_program(statements) - end - - # Visit a RangeNode node. - def visit_range_node(node) - left = visit(node.left) - right = visit(node.right) - - bounds(node.location) - if node.exclude_end? - on_dot3(left, right) - else - on_dot2(left, right) - end - end - - # Visit a RationalNode node. - def visit_rational_node(node) - visit_number(node) { |text| on_rational(text) } - end - - # Visit a StringNode node. - def visit_string_node(node) - bounds(node.content_loc) - tstring_val = on_tstring_content(node.unescaped.to_s) - on_string_literal(on_string_add(on_string_content, tstring_val)) - end - - # Visit an XStringNode node. - def visit_x_string_node(node) - bounds(node.content_loc) - tstring_val = on_tstring_content(node.unescaped.to_s) - on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val)) - end - - # Visit an InterpolatedStringNode node. - def visit_interpolated_string_node(node) - parts = node.parts.map do |part| - case part - when StringNode - bounds(part.content_loc) - on_tstring_content(part.content) - when EmbeddedStatementsNode - on_string_embexpr(visit(part)) - else - raise NotImplementedError, "Unexpected node type in InterpolatedStringNode" - end - end - - string_list = parts.inject(on_string_content) do |items, item| - on_string_add(items, item) - end - - on_string_literal(string_list) - end - - # Visit an EmbeddedStatementsNode node. - def visit_embedded_statements_node(node) - visit(node.statements) - end - - # Visit a SymbolNode node. - def visit_symbol_node(node) - if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s")) - bounds(node.value_loc) - tstring_val = on_tstring_content(node.value.to_s) - return on_dyna_symbol(on_string_add(on_string_content, tstring_val)) - end - - bounds(node.value_loc) - ident_val = on_ident(node.value.to_s) - on_symbol_literal(on_symbol(ident_val)) - end - - # Visit a StatementsNode node. - def visit_statements_node(node) - bounds(node.location) - node.body.inject(on_stmts_new) do |stmts, stmt| - on_stmts_add(stmts, visit(stmt)) - end - end - - ############################################################################ - # Entrypoints for subclasses - ############################################################################ - - # This is a convenience method that runs the SexpBuilder subclass parser. - def self.sexp_raw(source) - SexpBuilder.new(source).parse - end - - # This is a convenience method that runs the SexpBuilderPP subclass parser. - def self.sexp(source) - SexpBuilderPP.new(source).parse - end - - private - - # Generate Ripper events for a CallNode with no opening_loc - def visit_no_paren_call(node) - # No opening_loc can mean an operator. It can also mean a - # method call with no parentheses. - if node.message.match?(/^[[:punct:]]/) - left = visit(node.receiver) - if node.arguments&.arguments&.length == 1 - right = visit(node.arguments.arguments.first) - - return on_binary(left, node.name, right) - elsif !node.arguments || node.arguments.empty? - return on_unary(node.name, left) - else - raise NotImplementedError, "More than two arguments for operator" - end - elsif node.call_operator_loc.nil? - # In Ripper a method call like "puts myvar" with no parentheses is a "command". - bounds(node.message_loc) - ident_val = on_ident(node.message) - - # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") - if node.block - block_val = visit(node.block) - # In these calls, even if node.arguments is nil, we still get an :args_new call. - args = if node.arguments.nil? - on_args_new - else - on_args_add_block(visit_elements(node.arguments.arguments)) - end - method_args_val = on_method_add_arg(on_fcall(ident_val), args) - return on_method_add_block(method_args_val, block_val) - else - if node.arguments.nil? - return on_command(ident_val, nil) - else - args = on_args_add_block(visit_elements(node.arguments.arguments), false) - return on_command(ident_val, args) - end - end - else - operator = node.call_operator_loc.slice - if operator == "." || operator == "&." - left_val = visit(node.receiver) - - bounds(node.call_operator_loc) - operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) - - bounds(node.message_loc) - right_val = on_ident(node.message) - - call_val = on_call(left_val, operator_val, right_val) - - if node.block - block_val = visit(node.block) - return on_method_add_block(call_val, block_val) - else - return call_val - end - else - raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" - end - end - end - - # Visit a list of elements, like the elements of an array or arguments. - def visit_elements(elements) - bounds(elements.first.location) - elements.inject(on_args_new) do |args, element| - on_args_add(args, visit(element)) - end - end - - # Visit an operation-and-assign node, such as +=. - def visit_binary_op_assign(node, operator: node.operator) - bounds(node.name_loc) - ident_val = on_ident(node.name.to_s) - - bounds(node.operator_loc) - op_val = on_op(operator) - - on_opassign(on_var_field(ident_val), op_val, visit(node.value)) - end - - # In Prism this is a CallNode with :[] as the operator. - # In Ripper it's an :aref. - def visit_aref_node(node) - first_arg_val = visit(node.arguments.arguments[0]) - args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) - on_aref(visit(node.receiver), args_val) - end - - # In Prism this is a CallNode with :[]= as the operator. - # In Ripper it's an :aref_field. - def visit_aref_field_node(node) - first_arg_val = visit(node.arguments.arguments[0]) - args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) - assign_val = visit(node.arguments.arguments[1]) - on_assign(on_aref_field(visit(node.receiver), args_val), assign_val) - end - - # Visit a node that represents a number. We need to explicitly handle the - # unary - operator. - def visit_number(node) - slice = node.slice - location = node.location - - if slice[0] == "-" - bounds_values(location.start_line, location.start_column + 1) - value = yield slice[1..-1] - - bounds(node.location) - on_unary(visit_unary_operator(:-@), value) - else - bounds(location) - yield slice - end - end - - if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0") - # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@ - def visit_unary_operator(value) - value == :-@ ? :- : value - end - else - # For most Rubies and JRuby after 9.4.6.0 this is a no-op. - def visit_unary_operator(value) - value - end - end - - if RUBY_ENGINE == "jruby" - # For JRuby, "no block" in an on_block_var is nil - def no_block_value - nil - end - else - # For CRuby et al, "no block" in an on_block_var is false - def no_block_value - false - end - end - - # Visit a binary operator node like an AndNode or OrNode - def visit_binary_operator(node) - left_val = visit(node.left) - right_val = visit(node.right) - on_binary(left_val, node.operator.to_sym, right_val) - end - - # This method is responsible for updating lineno and column information - # to reflect the current node. - # - # This method could be drastically improved with some caching on the start - # of every line, but for now it's good enough. - def bounds(location) - @lineno = location.start_line - @column = location.start_column - end - - # If we need to do something unusual, we can directly update the line number - # and column to reflect the current node. - def bounds_values(lineno, column) - @lineno = lineno - @column = column - end - - # Lazily initialize the parse result. - def result - @result ||= Prism.parse(source) - end - - def _dispatch0; end # :nodoc: - def _dispatch1(_); end # :nodoc: - def _dispatch2(_, _); end # :nodoc: - def _dispatch3(_, _, _); end # :nodoc: - def _dispatch4(_, _, _, _); end # :nodoc: - def _dispatch5(_, _, _, _, _); end # :nodoc: - def _dispatch7(_, _, _, _, _, _, _); end # :nodoc: - - alias_method :on_parse_error, :_dispatch1 - alias_method :on_magic_comment, :_dispatch2 - - (Ripper::SCANNER_EVENT_TABLE.merge(Ripper::PARSER_EVENT_TABLE)).each do |event, arity| - alias_method :"on_#{event}", :"_dispatch#{arity}" - end - end -end diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 9a7cedac46a4ea..31d4cd7b7f5202 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -7,5 +7,6 @@ module Prism # seattlerb/ruby_parser gem's syntax tree as well. module Translation autoload :Parser, "prism/translation/parser" + autoload :Ripper, "prism/translation/ripper" end end diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb new file mode 100644 index 00000000000000..e0b76abded7855 --- /dev/null +++ b/lib/prism/translation/ripper.rb @@ -0,0 +1,577 @@ +# frozen_string_literal: true + +require "ripper" + +module Prism + module Translation + # Note: This integration is not finished, and therefore still has many + # inconsistencies with Ripper. If you'd like to help out, pull requests would + # be greatly appreciated! + # + # This class is meant to provide a compatibility layer between prism and + # Ripper. It functions by parsing the entire tree first and then walking it + # and executing each of the Ripper callbacks as it goes. + # + # This class is going to necessarily be slower than the native Ripper API. It + # is meant as a stopgap until developers migrate to using prism. It is also + # meant as a test harness for the prism parser. + # + # To use this class, you treat `Prism::Translation::Ripper` effectively as you would + # treat the `Ripper` class. + class Ripper < Compiler + # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that + # returns the arrays of [type, *children]. + class SexpBuilder < Ripper + private + + ::Ripper::PARSER_EVENTS.each do |event| + define_method(:"on_#{event}") do |*args| + [event, *args] + end + end + + ::Ripper::SCANNER_EVENTS.each do |event| + define_method(:"on_#{event}") do |value| + [:"@#{event}", value, [lineno, column]] + end + end + end + + # This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that + # returns the same values as ::Ripper::SexpBuilder except with a couple of + # niceties that flatten linked lists into arrays. + class SexpBuilderPP < SexpBuilder + private + + def _dispatch_event_new # :nodoc: + [] + end + + def _dispatch_event_push(list, item) # :nodoc: + list << item + list + end + + ::Ripper::PARSER_EVENT_TABLE.each do |event, arity| + case event + when /_new\z/ + alias_method :"on_#{event}", :_dispatch_event_new if arity == 0 + when /_add\z/ + alias_method :"on_#{event}", :_dispatch_event_push + end + end + end + + # The source that is being parsed. + attr_reader :source + + # The current line number of the parser. + attr_reader :lineno + + # The current column number of the parser. + attr_reader :column + + # Create a new Translation::Ripper object with the given source. + def initialize(source) + @source = source + @result = nil + @lineno = nil + @column = nil + end + + ############################################################################ + # Public interface + ############################################################################ + + # True if the parser encountered an error during parsing. + def error? + result.failure? + end + + # Parse the source and return the result. + def parse + result.magic_comments.each do |magic_comment| + on_magic_comment(magic_comment.key, magic_comment.value) + end + + if error? + result.errors.each do |error| + on_parse_error(error.message) + end + + nil + else + result.value.accept(self) + end + end + + ############################################################################ + # Visitor methods + ############################################################################ + + # Visit an ArrayNode node. + def visit_array_node(node) + elements = visit_elements(node.elements) unless node.elements.empty? + bounds(node.location) + on_array(elements) + end + + # Visit a CallNode node. + # Ripper distinguishes between many different method-call + # nodes -- unary and binary operators, "command" calls with + # no parentheses, and call/fcall/vcall. + def visit_call_node(node) + return visit_aref_node(node) if node.name == :[] + return visit_aref_field_node(node) if node.name == :[]= + + if node.variable_call? + raise NotImplementedError unless node.receiver.nil? + + bounds(node.message_loc) + return on_vcall(on_ident(node.message)) + end + + if node.opening_loc.nil? + return visit_no_paren_call(node) + end + + # A non-operator method call with parentheses + + args = if node.arguments.nil? + on_arg_paren(nil) + else + on_arg_paren(on_args_add_block(visit_elements(node.arguments.arguments), false)) + end + + bounds(node.message_loc) + ident_val = on_ident(node.message) + + bounds(node.location) + args_call_val = on_method_add_arg(on_fcall(ident_val), args) + if node.block + block_val = visit(node.block) + + return on_method_add_block(args_call_val, block_val) + else + return args_call_val + end + end + + # Visit a LocalVariableWriteNode. + def visit_local_variable_write_node(node) + bounds(node.name_loc) + ident_val = on_ident(node.name.to_s) + on_assign(on_var_field(ident_val), visit(node.value)) + end + + # Visit a LocalVariableAndWriteNode. + def visit_local_variable_and_write_node(node) + visit_binary_op_assign(node) + end + + # Visit a LocalVariableOrWriteNode. + def visit_local_variable_or_write_node(node) + visit_binary_op_assign(node) + end + + # Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes. + def visit_local_variable_operator_write_node(node) + visit_binary_op_assign(node, operator: "#{node.operator}=") + end + + # Visit a LocalVariableReadNode. + def visit_local_variable_read_node(node) + bounds(node.location) + ident_val = on_ident(node.slice) + + on_var_ref(ident_val) + end + + # Visit a BlockNode. + def visit_block_node(node) + params_val = node.parameters.nil? ? nil : visit(node.parameters) + + body_val = node.body.nil? ? on_stmts_add(on_stmts_new, on_void_stmt) : visit(node.body) + + on_brace_block(params_val, body_val) + end + + # Visit a BlockParametersNode. + def visit_block_parameters_node(node) + on_block_var(visit(node.parameters), no_block_value) + end + + # Visit a ParametersNode. + # This will require expanding as we support more kinds of parameters. + def visit_parameters_node(node) + #on_params(required, optional, nil, nil, nil, nil, nil) + on_params(visit_all(node.requireds), nil, nil, nil, nil, nil, nil) + end + + # Visit a RequiredParameterNode. + def visit_required_parameter_node(node) + bounds(node.location) + on_ident(node.name.to_s) + end + + # Visit a BreakNode. + def visit_break_node(node) + return on_break(on_args_new) if node.arguments.nil? + + args_val = visit_elements(node.arguments.arguments) + on_break(on_args_add_block(args_val, false)) + end + + # Visit an AndNode. + def visit_and_node(node) + visit_binary_operator(node) + end + + # Visit an OrNode. + def visit_or_node(node) + visit_binary_operator(node) + end + + # Visit a TrueNode. + def visit_true_node(node) + bounds(node.location) + on_var_ref(on_kw("true")) + end + + # Visit a FalseNode. + def visit_false_node(node) + bounds(node.location) + on_var_ref(on_kw("false")) + end + + # Visit a FloatNode node. + def visit_float_node(node) + visit_number(node) { |text| on_float(text) } + end + + # Visit a ImaginaryNode node. + def visit_imaginary_node(node) + visit_number(node) { |text| on_imaginary(text) } + end + + # Visit an IntegerNode node. + def visit_integer_node(node) + visit_number(node) { |text| on_int(text) } + end + + # Visit a ParenthesesNode node. + def visit_parentheses_node(node) + body = + if node.body.nil? + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.body) + end + + bounds(node.location) + on_paren(body) + end + + # Visit a BeginNode node. + # This is not at all bulletproof against different structures of begin/rescue/else/ensure/end. + def visit_begin_node(node) + rescue_val = node.rescue_clause ? on_rescue(nil, nil, visit(node.rescue_clause), nil) : nil + ensure_val = node.ensure_clause ? on_ensure(visit(node.ensure_clause.statements)) : nil + on_begin(on_bodystmt(visit(node.statements), rescue_val, nil, ensure_val)) + end + + # Visit a RescueNode node. + def visit_rescue_node(node) + visit(node.statements) + end + + # Visit a ProgramNode node. + def visit_program_node(node) + statements = visit(node.statements) + bounds(node.location) + on_program(statements) + end + + # Visit a RangeNode node. + def visit_range_node(node) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + if node.exclude_end? + on_dot3(left, right) + else + on_dot2(left, right) + end + end + + # Visit a RationalNode node. + def visit_rational_node(node) + visit_number(node) { |text| on_rational(text) } + end + + # Visit a StringNode node. + def visit_string_node(node) + bounds(node.content_loc) + tstring_val = on_tstring_content(node.unescaped.to_s) + on_string_literal(on_string_add(on_string_content, tstring_val)) + end + + # Visit an XStringNode node. + def visit_x_string_node(node) + bounds(node.content_loc) + tstring_val = on_tstring_content(node.unescaped.to_s) + on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val)) + end + + # Visit an InterpolatedStringNode node. + def visit_interpolated_string_node(node) + parts = node.parts.map do |part| + case part + when StringNode + bounds(part.content_loc) + on_tstring_content(part.content) + when EmbeddedStatementsNode + on_string_embexpr(visit(part)) + else + raise NotImplementedError, "Unexpected node type in InterpolatedStringNode" + end + end + + string_list = parts.inject(on_string_content) do |items, item| + on_string_add(items, item) + end + + on_string_literal(string_list) + end + + # Visit an EmbeddedStatementsNode node. + def visit_embedded_statements_node(node) + visit(node.statements) + end + + # Visit a SymbolNode node. + def visit_symbol_node(node) + if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s")) + bounds(node.value_loc) + tstring_val = on_tstring_content(node.value.to_s) + return on_dyna_symbol(on_string_add(on_string_content, tstring_val)) + end + + bounds(node.value_loc) + ident_val = on_ident(node.value.to_s) + on_symbol_literal(on_symbol(ident_val)) + end + + # Visit a StatementsNode node. + def visit_statements_node(node) + bounds(node.location) + node.body.inject(on_stmts_new) do |stmts, stmt| + on_stmts_add(stmts, visit(stmt)) + end + end + + ############################################################################ + # Entrypoints for subclasses + ############################################################################ + + # This is a convenience method that runs the SexpBuilder subclass parser. + def self.sexp_raw(source) + SexpBuilder.new(source).parse + end + + # This is a convenience method that runs the SexpBuilderPP subclass parser. + def self.sexp(source) + SexpBuilderPP.new(source).parse + end + + private + + # Generate Ripper events for a CallNode with no opening_loc + def visit_no_paren_call(node) + # No opening_loc can mean an operator. It can also mean a + # method call with no parentheses. + if node.message.match?(/^[[:punct:]]/) + left = visit(node.receiver) + if node.arguments&.arguments&.length == 1 + right = visit(node.arguments.arguments.first) + + return on_binary(left, node.name, right) + elsif !node.arguments || node.arguments.empty? + return on_unary(node.name, left) + else + raise NotImplementedError, "More than two arguments for operator" + end + elsif node.call_operator_loc.nil? + # In Ripper a method call like "puts myvar" with no parentheses is a "command". + bounds(node.message_loc) + ident_val = on_ident(node.message) + + # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") + if node.block + block_val = visit(node.block) + # In these calls, even if node.arguments is nil, we still get an :args_new call. + args = if node.arguments.nil? + on_args_new + else + on_args_add_block(visit_elements(node.arguments.arguments)) + end + method_args_val = on_method_add_arg(on_fcall(ident_val), args) + return on_method_add_block(method_args_val, block_val) + else + if node.arguments.nil? + return on_command(ident_val, nil) + else + args = on_args_add_block(visit_elements(node.arguments.arguments), false) + return on_command(ident_val, args) + end + end + else + operator = node.call_operator_loc.slice + if operator == "." || operator == "&." + left_val = visit(node.receiver) + + bounds(node.call_operator_loc) + operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) + + bounds(node.message_loc) + right_val = on_ident(node.message) + + call_val = on_call(left_val, operator_val, right_val) + + if node.block + block_val = visit(node.block) + return on_method_add_block(call_val, block_val) + else + return call_val + end + else + raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" + end + end + end + + # Visit a list of elements, like the elements of an array or arguments. + def visit_elements(elements) + bounds(elements.first.location) + elements.inject(on_args_new) do |args, element| + on_args_add(args, visit(element)) + end + end + + # Visit an operation-and-assign node, such as +=. + def visit_binary_op_assign(node, operator: node.operator) + bounds(node.name_loc) + ident_val = on_ident(node.name.to_s) + + bounds(node.operator_loc) + op_val = on_op(operator) + + on_opassign(on_var_field(ident_val), op_val, visit(node.value)) + end + + # In Prism this is a CallNode with :[] as the operator. + # In Ripper it's an :aref. + def visit_aref_node(node) + first_arg_val = visit(node.arguments.arguments[0]) + args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) + on_aref(visit(node.receiver), args_val) + end + + # In Prism this is a CallNode with :[]= as the operator. + # In Ripper it's an :aref_field. + def visit_aref_field_node(node) + first_arg_val = visit(node.arguments.arguments[0]) + args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) + assign_val = visit(node.arguments.arguments[1]) + on_assign(on_aref_field(visit(node.receiver), args_val), assign_val) + end + + # Visit a node that represents a number. We need to explicitly handle the + # unary - operator. + def visit_number(node) + slice = node.slice + location = node.location + + if slice[0] == "-" + bounds_values(location.start_line, location.start_column + 1) + value = yield slice[1..-1] + + bounds(node.location) + on_unary(visit_unary_operator(:-@), value) + else + bounds(location) + yield slice + end + end + + if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0") + # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@ + def visit_unary_operator(value) + value == :-@ ? :- : value + end + else + # For most Rubies and JRuby after 9.4.6.0 this is a no-op. + def visit_unary_operator(value) + value + end + end + + if RUBY_ENGINE == "jruby" + # For JRuby, "no block" in an on_block_var is nil + def no_block_value + nil + end + else + # For CRuby et al, "no block" in an on_block_var is false + def no_block_value + false + end + end + + # Visit a binary operator node like an AndNode or OrNode + def visit_binary_operator(node) + left_val = visit(node.left) + right_val = visit(node.right) + on_binary(left_val, node.operator.to_sym, right_val) + end + + # This method is responsible for updating lineno and column information + # to reflect the current node. + # + # This method could be drastically improved with some caching on the start + # of every line, but for now it's good enough. + def bounds(location) + @lineno = location.start_line + @column = location.start_column + end + + # If we need to do something unusual, we can directly update the line number + # and column to reflect the current node. + def bounds_values(lineno, column) + @lineno = lineno + @column = column + end + + # Lazily initialize the parse result. + def result + @result ||= Prism.parse(source) + end + + def _dispatch0; end # :nodoc: + def _dispatch1(_); end # :nodoc: + def _dispatch2(_, _); end # :nodoc: + def _dispatch3(_, _, _); end # :nodoc: + def _dispatch4(_, _, _, _); end # :nodoc: + def _dispatch5(_, _, _, _, _); end # :nodoc: + def _dispatch7(_, _, _, _, _, _, _); end # :nodoc: + + alias_method :on_parse_error, :_dispatch1 + alias_method :on_magic_comment, :_dispatch2 + + (::Ripper::SCANNER_EVENT_TABLE.merge(::Ripper::PARSER_EVENT_TABLE)).each do |event, arity| + alias_method :"on_#{event}", :"_dispatch#{arity}" + end + end + end +end diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_test.rb similarity index 95% rename from test/prism/ripper_compat_test.rb rename to test/prism/ripper_test.rb index 40c609d58ca824..7abb78c72388be 100644 --- a/test/prism/ripper_compat_test.rb +++ b/test/prism/ripper_test.rb @@ -3,7 +3,7 @@ require_relative "test_helper" module Prism - class RipperCompatTest < TestCase + class RipperTest < TestCase def test_binary assert_equivalent("1 + 2") assert_equivalent("3 - 4 * 5") @@ -148,11 +148,11 @@ def assert_equivalent(source) expected = Ripper.sexp_raw(source) refute_nil expected - assert_equal expected, RipperCompat.sexp_raw(source) + assert_equal expected, Prism::Translation::Ripper.sexp_raw(source) end end - class RipperCompatFixturesTest < TestCase + class RipperFixturesTest < TestCase #base = File.join(__dir__, "fixtures") #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base] relatives = [ @@ -177,7 +177,7 @@ class RipperCompatFixturesTest < TestCase puts "Could not parse #{path.inspect}!" end refute_nil expected - assert_equal expected, RipperCompat.sexp_raw(source) + assert_equal expected, Translation::Ripper.sexp_raw(source) end end