aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYusuke Endoh <mame@ruby-lang.org>2021-06-18 17:11:39 +0900
committerYusuke Endoh <mame@ruby-lang.org>2021-06-29 23:45:49 +0900
commite94604966572bb43fc887856d54aa54b8e9f7719 (patch)
tree789d92c232f671c6620af5c4bfd1a3fc0d9d26b3
parent03dc66449326ce0945c1ccad7f51e57125b2b854 (diff)
downloadruby-e94604966572bb43fc887856d54aa54b8e9f7719.tar.gz
[WIP] add error_squiggle gem
``` $ ./local/bin/ruby -e '1.time {}' -e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError) 1.time {} ^^^^^ Did you mean? times ``` https://bugs.ruby-lang.org/issues/17930
-rw-r--r--gem_prelude.rb6
-rw-r--r--lib/error_squiggle.rb2
-rw-r--r--lib/error_squiggle/base.rb446
-rw-r--r--lib/error_squiggle/core_ext.rb48
-rw-r--r--lib/error_squiggle/version.rb3
-rw-r--r--ruby.c6
-rw-r--r--spec/ruby/core/exception/no_method_error_spec.rb4
-rw-r--r--test/error_squiggle/test_error_squiggle.rb984
-rw-r--r--test/ruby/marshaltestlib.rb2
-rw-r--r--test/ruby/test_marshal.rb2
-rw-r--r--test/ruby/test_module.rb2
-rw-r--r--test/ruby/test_name_error.rb2
-rw-r--r--test/ruby/test_nomethod_error.rb2
-rw-r--r--test/ruby/test_object.rb2
14 files changed, 1503 insertions, 8 deletions
diff --git a/gem_prelude.rb b/gem_prelude.rb
index c4debb6509..f5616e6b68 100644
--- a/gem_prelude.rb
+++ b/gem_prelude.rb
@@ -5,6 +5,12 @@ rescue LoadError
end if defined?(Gem)
begin
+ require 'error_squiggle'
+rescue LoadError
+ warn "`error_squiggle' was not loaded."
+end if defined?(ErrorSquiggle)
+
+begin
require 'did_you_mean'
rescue LoadError
warn "`did_you_mean' was not loaded."
diff --git a/lib/error_squiggle.rb b/lib/error_squiggle.rb
new file mode 100644
index 0000000000..02b01932c3
--- /dev/null
+++ b/lib/error_squiggle.rb
@@ -0,0 +1,2 @@
+require_relative "error_squiggle/base"
+require_relative "error_squiggle/core_ext"
diff --git a/lib/error_squiggle/base.rb b/lib/error_squiggle/base.rb
new file mode 100644
index 0000000000..1ff2db9797
--- /dev/null
+++ b/lib/error_squiggle/base.rb
@@ -0,0 +1,446 @@
+require_relative "version"
+
+module ErrorSquiggle
+ # Identify the code fragment that seems associated with a given error
+ #
+ # Arguments:
+ # node: RubyVM::AbstractSyntaxTree::Node
+ # point: :name | :args
+ # name: The name associated with the NameError/NoMethodError
+ # fetch: A block to fetch a specified code line (or lines)
+ #
+ # Returns:
+ # {
+ # first_lineno: Integer,
+ # first_column: Integer,
+ # last_lineno: Integer,
+ # last_column: Integer,
+ # line: String,
+ # } | nil
+ def self.spot(...)
+ Spotter.new(...).spot
+ end
+
+ class Spotter
+ def initialize(node, point, name: nil, &fetch)
+ @node = node
+ @point = point
+ @name = name
+
+ # Not-implemented-yet options
+ @arg = nil # Specify the index or keyword at which argument caused the TypeError/ArgumentError
+ @multiline = false # Allow multiline spot
+
+ @fetch = fetch
+ end
+
+ def spot
+ return nil unless @node
+
+ case @node.type
+
+ when :CALL, :QCALL
+ case @point
+ when :name
+ spot_call_for_name
+ when :args
+ spot_call_for_args
+ end
+
+ when :ATTRASGN
+ case @point
+ when :name
+ spot_attrasgn_for_name
+ when :args
+ spot_attrasgn_for_args
+ end
+
+ when :OPCALL
+ case @point
+ when :name
+ spot_opcall_for_name
+ when :args
+ spot_opcall_for_args
+ end
+
+ when :FCALL
+ case @point
+ when :name
+ spot_fcall_for_name
+ when :args
+ spot_fcall_for_args
+ end
+
+ when :VCALL
+ spot_vcall
+
+ when :OP_ASGN1
+ case @point
+ when :name
+ spot_op_asgn1_for_name
+ when :args
+ spot_op_asgn1_for_args
+ end
+
+ when :OP_ASGN2
+ case @point
+ when :name
+ spot_op_asgn2_for_name
+ when :args
+ spot_op_asgn2_for_args
+ end
+
+ when :CONST
+ spot_vcall
+
+ when :COLON2
+ spot_colon2
+
+ when :COLON3
+ spot_vcall
+
+ when :OP_CDECL
+ spot_op_cdecl
+ end
+
+ if @line && @beg_column && @end_column && @beg_column < @end_column
+ return {
+ first_lineno: @beg_lineno,
+ first_column: @beg_column,
+ last_lineno: @end_lineno,
+ last_column: @end_column,
+ line: @line,
+ }
+ else
+ return nil
+ end
+ end
+
+ private
+
+ # Example:
+ # x.foo
+ # ^^^^
+ # x.foo(42)
+ # ^^^^
+ # x&.foo
+ # ^^^^^
+ # x[42]
+ # ^^^^
+ # x += 1
+ # ^
+ def spot_call_for_name
+ nd_recv, mid, nd_args = @node.children
+ lineno = nd_recv.last_lineno
+ lines = @fetch[lineno, @node.last_lineno]
+ if mid == :[] && lines.match(/\G\s*(\[(?:\s*\])?)/, nd_recv.last_column)
+ @beg_column = $~.begin(1)
+ @line = lines[/.*\n/]
+ @beg_lineno = @end_lineno = lineno
+ if nd_args
+ if nd_recv.last_lineno == nd_args.last_lineno && @line.match(/\s*\]/, nd_args.last_column)
+ @end_column = $~.end(0)
+ end
+ else
+ if lines.match(/\G\s*?\[\s*\]/, nd_recv.last_column)
+ @end_column = $~.end(0)
+ end
+ end
+ elsif lines.match(/\G\s*?(\&?\.)(\s*?)(#{ Regexp.quote(mid) }).*\n/, nd_recv.last_column)
+ lines = $` + $&
+ @beg_column = $~.begin($2.include?("\n") ? 3 : 1)
+ @end_column = $~.end(3)
+ if i = lines[..@beg_column].rindex("\n")
+ @beg_lineno = @end_lineno = lineno + lines[..@beg_column].count("\n")
+ @line = lines[i + 1..]
+ @beg_column -= i + 1
+ @end_column -= i + 1
+ else
+ @line = lines
+ @beg_lineno = @end_lineno = lineno
+ end
+ elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column)
+ @line = $` + $&
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+
+ # Example:
+ # x.foo(42)
+ # ^^
+ # x[42]
+ # ^^
+ # x += 1
+ # ^
+ def spot_call_for_args
+ _nd_recv, _mid, nd_args = @node.children
+ if nd_args && nd_args.first_lineno == nd_args.last_lineno
+ fetch_line(nd_args.first_lineno)
+ @beg_column = nd_args.first_column
+ @end_column = nd_args.last_column
+ end
+ # TODO: support @arg
+ end
+
+ # Example:
+ # x.foo = 1
+ # ^^^^^^
+ # x[42] = 1
+ # ^^^^^^
+ def spot_attrasgn_for_name
+ nd_recv, mid, nd_args = @node.children
+ *nd_args, _nd_last_arg, _nil = nd_args.children
+ fetch_line(nd_recv.last_lineno)
+ if mid == :[]= && @line.match(/\G\s*(\[)/, nd_recv.last_column)
+ @beg_column = $~.begin(1)
+ args_last_column = $~.end(0)
+ if nd_args.last && nd_recv.last_lineno == nd_args.last.last_lineno
+ args_last_column = nd_args.last.last_column
+ end
+ if @line.match(/\s*\]\s*=/, args_last_column)
+ @end_column = $~.end(0)
+ end
+ elsif @line.match(/\G\s*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+
+ # Example:
+ # x.foo = 1
+ # ^
+ # x[42] = 1
+ # ^^^^^^^
+ # x[] = 1
+ # ^^^^^
+ def spot_attrasgn_for_args
+ nd_recv, mid, nd_args = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
+ @beg_column = $~.end(0)
+ if nd_recv.last_lineno == nd_args.last_lineno
+ @end_column = nd_args.last_column
+ end
+ elsif nd_args && nd_args.first_lineno == nd_args.last_lineno
+ @beg_column = nd_args.first_column
+ @end_column = nd_args.last_column
+ end
+ # TODO: support @arg
+ end
+
+ # Example:
+ # x + 1
+ # ^
+ # +x
+ # ^
+ def spot_opcall_for_name
+ nd_recv, op, nd_arg = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if nd_arg
+ # binary operator
+ if @line.match(/\G\s*(#{ Regexp.quote(op) })/, nd_recv.last_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ else
+ # unary operator
+ if @line[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+ end
+
+ # Example:
+ # x + 1
+ # ^
+ def spot_opcall_for_args
+ _nd_recv, _op, nd_arg = @node.children
+ if nd_arg && nd_arg.first_lineno == nd_arg.last_lineno
+ # binary operator
+ fetch_line(nd_arg.first_lineno)
+ @beg_column = nd_arg.first_column
+ @end_column = nd_arg.last_column
+ end
+ end
+
+ # Example:
+ # foo(42)
+ # ^^^
+ # foo 42
+ # ^^^
+ def spot_fcall_for_name
+ mid, _nd_args = @node.children
+ fetch_line(@node.first_lineno)
+ if @line.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+
+ # Example:
+ # foo(42)
+ # ^^
+ # foo 42
+ # ^^
+ def spot_fcall_for_args
+ _mid, nd_args = @node.children
+ if nd_args && nd_args.first_lineno == nd_args.last_lineno
+ # binary operator
+ fetch_line(nd_args.first_lineno)
+ @beg_column = nd_args.first_column
+ @end_column = nd_args.last_column
+ end
+ end
+
+ # Example:
+ # foo
+ # ^^^
+ def spot_vcall
+ if @node.first_lineno == @node.last_lineno
+ fetch_line(@node.last_lineno)
+ @beg_column = @node.first_column
+ @end_column = @node.last_column
+ end
+ end
+
+ # Example:
+ # x[1] += 42
+ # ^^^ (for [])
+ # x[1] += 42
+ # ^ (for +)
+ # x[1] += 42
+ # ^^^^^^ (for []=)
+ def spot_op_asgn1_for_name
+ nd_recv, op, nd_args, _nd_rhs = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if @line.match(/\G\s*(\[)/, nd_recv.last_column)
+ bracket_beg_column = $~.begin(1)
+ args_last_column = $~.end(0)
+ if nd_args && nd_recv.last_lineno == nd_args.last_lineno
+ args_last_column = nd_args.last_column
+ end
+ if @line.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
+ case @name
+ when :[], :[]=
+ @beg_column = bracket_beg_column
+ @end_column = $~.begin(@name == :[] ? 1 : 3)
+ when op
+ @beg_column = $~.begin(2)
+ @end_column = $~.end(2)
+ end
+ end
+ end
+ end
+
+ # Example:
+ # x[1] += 42
+ # ^^^^^^^^
+ def spot_op_asgn1_for_args
+ nd_recv, mid, nd_args, nd_rhs = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
+ @beg_column = $~.end(0)
+ if nd_recv.last_lineno == nd_rhs.last_lineno
+ @end_column = nd_rhs.last_column
+ end
+ elsif nd_args && nd_args.first_lineno == nd_rhs.last_lineno
+ @beg_column = nd_args.first_column
+ @end_column = nd_rhs.last_column
+ end
+ # TODO: support @arg
+ end
+
+ # Example:
+ # x.foo += 42
+ # ^^^ (for foo)
+ # x.foo += 42
+ # ^ (for +)
+ # x.foo += 42
+ # ^^^^^^^ (for foo=)
+ def spot_op_asgn2_for_name
+ nd_recv, _qcall, attr, op, _nd_rhs = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if @line.match(/\G\s*(\.)\s*#{ Regexp.quote(attr) }()\s*(#{ Regexp.quote(op) })(=)/, nd_recv.last_column)
+ case @name
+ when attr
+ @beg_column = $~.begin(1)
+ @end_column = $~.begin(2)
+ when op
+ @beg_column = $~.begin(3)
+ @end_column = $~.end(3)
+ when :"#{ attr }="
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(4)
+ end
+ end
+ end
+
+ # Example:
+ # x.foo += 42
+ # ^^
+ def spot_op_asgn2_for_args
+ _nd_recv, _qcall, _attr, _op, nd_rhs = @node.children
+ if nd_rhs.first_lineno == nd_rhs.last_lineno
+ fetch_line(nd_rhs.first_lineno)
+ @beg_column = nd_rhs.first_column
+ @end_column = nd_rhs.last_column
+ end
+ end
+
+ # Example:
+ # Foo::Bar
+ # ^^^^^
+ def spot_colon2
+ nd_parent, const = @node.children
+ if nd_parent.last_lineno == @node.last_lineno
+ fetch_line(nd_parent.last_lineno)
+ @beg_column = nd_parent.last_column
+ @end_column = @node.last_column
+ else
+ @line = @fetch[@node.last_lineno]
+ if @line[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
+ @beg_column = $~.begin(0)
+ @end_column = $~.end(0)
+ end
+ end
+ end
+
+ # Example:
+ # Foo::Bar += 1
+ # ^^^^^^^^
+ def spot_op_cdecl
+ nd_lhs, op, _nd_rhs = @node.children
+ *nd_parent_lhs, _const = nd_lhs.children
+ if @name == op
+ @line = @fetch[nd_lhs.last_lineno]
+ if @line.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ else
+ # constant access error
+ @end_column = nd_lhs.last_column
+ if nd_parent_lhs.empty? # example: ::C += 1
+ if nd_lhs.first_lineno == nd_lhs.last_lineno
+ @line = @fetch[nd_lhs.last_lineno]
+ @beg_column = nd_lhs.first_column
+ end
+ else # example: Foo::Bar::C += 1
+ if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno
+ @line = @fetch[nd_lhs.last_lineno]
+ @beg_column = nd_parent_lhs.last.last_column
+ end
+ end
+ end
+ end
+
+ def fetch_line(lineno)
+ @beg_lineno = @end_lineno = lineno
+ @line = @fetch[lineno]
+ end
+ end
+
+ private_constant :Spotter
+end
diff --git a/lib/error_squiggle/core_ext.rb b/lib/error_squiggle/core_ext.rb
new file mode 100644
index 0000000000..6274eecb77
--- /dev/null
+++ b/lib/error_squiggle/core_ext.rb
@@ -0,0 +1,48 @@
+module ErrorSquiggle
+ module CoreExt
+ SKIP_TO_S_FOR_SUPER_LOOKUP = true
+ private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
+
+ def to_s
+ msg = super.dup
+
+ locs = backtrace_locations
+ return msg unless locs
+
+ loc = locs.first
+ begin
+ node = RubyVM::AbstractSyntaxTree.of(loc, save_script_lines: true)
+ opts = {}
+
+ case self
+ when NoMethodError, NameError
+ point = :name
+ opts[:name] = name
+ when TypeError, ArgumentError
+ point = :args
+ end
+
+ spot = ErrorSquiggle.spot(node, point, **opts) do |lineno, last_lineno|
+ last_lineno ||= lineno
+ node.script_lines[lineno - 1 .. last_lineno - 1].join("")
+ end
+
+ rescue Errno::ENOENT
+ end
+
+ if spot
+ marker = " " * spot[:first_column] + "^" * (spot[:last_column] - spot[:first_column])
+ points = "\n\n#{ spot[:line] }#{ marker }"
+ msg << points if !msg.include?(points)
+ end
+
+ msg
+ end
+ end
+
+ NameError.prepend(CoreExt)
+
+ # temporarily disabled
+ #TypeError.prepend(CoreExt)
+ #ArgumentError.prepend(CoreExt)
+end
diff --git a/lib/error_squiggle/version.rb b/lib/error_squiggle/version.rb
new file mode 100644
index 0000000000..4d88f3a9cf
--- /dev/null
+++ b/lib/error_squiggle/version.rb
@@ -0,0 +1,3 @@
+module ErrorSquiggle
+ VERSION = "0.1.0"
+end
diff --git a/ruby.c b/ruby.c
index baa2f251c2..c8b3964c39 100644
--- a/ruby.c
+++ b/ruby.c
@@ -94,6 +94,8 @@ void rb_warning_category_update(unsigned int mask, unsigned int bits);
#define EACH_FEATURES(X, SEP) \
X(gems) \
SEP \
+ X(error_squiggle) \
+ SEP \
X(did_you_mean) \
SEP \
X(rubyopt) \
@@ -320,6 +322,7 @@ usage(const char *name, int help, int highlight, int columns)
};
static const struct message features[] = {
M("gems", "", "rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")"),
+ M("error_squiggle", "", "error_squiggle (default: "DEFAULT_RUBYGEMS_ENABLED")"),
M("did_you_mean", "", "did_you_mean (default: "DEFAULT_RUBYGEMS_ENABLED")"),
M("rubyopt", "", "RUBYOPT environment variable (default: enabled)"),
M("frozen-string-literal", "", "freeze all string literals (default: disabled)"),
@@ -1508,6 +1511,9 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
if (opt->features.set & FEATURE_BIT(gems)) {
rb_define_module("Gem");
+ if (opt->features.set & FEATURE_BIT(error_squiggle)) {
+ rb_define_module("ErrorSquiggle");
+ }
if (opt->features.set & FEATURE_BIT(did_you_mean)) {
rb_define_module("DidYouMean");
}
diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb
index 570ffc47b1..a93ef2187e 100644
--- a/spec/ruby/core/exception/no_method_error_spec.rb
+++ b/spec/ruby/core/exception/no_method_error_spec.rb
@@ -110,14 +110,14 @@ describe "NoMethodError#message" do
begin
klass.foo
rescue NoMethodError => error
- error.message.lines.first.should == "undefined method `foo' for MyClass:Class"
+ error.message.lines.first.chomp.should == "undefined method `foo' for MyClass:Class"
end
mod = Module.new { def self.name; "MyModule"; end }
begin
mod.foo
rescue NoMethodError => error
- error.message.lines.first.should == "undefined method `foo' for MyModule:Module"
+ error.message.lines.first.chomp.should == "undefined method `foo' for MyModule:Module"
end
end
end
diff --git a/test/error_squiggle/test_error_squiggle.rb b/test/error_squiggle/test_error_squiggle.rb
new file mode 100644
index 0000000000..3b44daf74c
--- /dev/null
+++ b/test/error_squiggle/test_error_squiggle.rb
@@ -0,0 +1,984 @@
+require "test/unit"
+
+require "error_squiggle"
+
+class ErrorSquiggleTest < Test::Unit::TestCase
+ class DummyFormatter
+ def message_for(corrections)
+ ""
+ end
+ end
+
+ def setup
+ if defined?(DidYouMean)
+ @did_you_mean_old_formatter = DidYouMean.formatter
+ DidYouMean.formatter = DummyFormatter
+ end
+ end
+
+ def teardown
+ if defined?(DidYouMean)
+ DidYouMean.formatter = @did_you_mean_old_formatter
+ end
+ end
+
+ def assert_error_message(klass, expected_msg, &blk)
+ err = assert_raise(klass, &blk)
+ assert_equal(expected_msg.chomp, err.message)
+ end
+
+ def test_CALL_noarg_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.foo + 1
+ ^^^^
+ END
+
+ nil.foo + 1
+ end
+ end
+
+ def test_CALL_noarg_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ .foo + 1
+ ^^^^
+ END
+
+ nil
+ .foo + 1
+ end
+ end
+
+ def test_CALL_noarg_3
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ foo + 1
+ ^^^
+ END
+
+ nil.
+ foo + 1
+ end
+ end
+
+ def test_CALL_arg_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.foo (42)
+ ^^^^
+ END
+
+ nil.foo (42)
+ end
+ end
+
+ def test_CALL_arg_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ .foo (
+ ^^^^
+ END
+
+ nil
+ .foo (
+ 42
+ )
+ end
+ end
+
+ def test_CALL_arg_3
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ foo (
+ ^^^
+ END
+
+ nil.
+ foo (
+ 42
+ )
+ end
+ end
+
+ def test_CALL_arg_4
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.foo(42)
+ ^^^^
+ END
+
+ nil.foo(42)
+ end
+ end
+
+ def test_CALL_arg_5
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ .foo(
+ ^^^^
+ END
+
+ nil
+ .foo(
+ 42
+ )
+ end
+ end
+
+ def test_CALL_arg_6
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ foo(
+ ^^^
+ END
+
+ nil.
+ foo(
+ 42
+ )
+ end
+ end
+
+ def test_QCALL_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for 1:Integer
+
+ 1&.foo
+ ^^^^^
+ END
+
+ 1&.foo
+ end
+ end
+
+ def test_QCALL_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for 1:Integer
+
+ 1&.foo(42)
+ ^^^^^
+ END
+
+ 1&.foo(42)
+ end
+ end
+
+ def test_CALL_aref_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+
+ nil [ ]
+ ^^^
+ END
+
+ nil [ ]
+ end
+ end
+
+ def test_CALL_aref_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+
+ nil [0]
+ ^^^
+ END
+
+ nil [0]
+ end
+ end
+
+ def test_CALL_aref_3
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+ END
+
+ nil [
+ 0
+ ]
+ end
+ end
+
+ def test_CALL_aref_4
+ v = Object.new
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for #{ v.inspect }
+
+ v &.[](0)
+ ^^^^
+ END
+
+ v &.[](0)
+ end
+ end
+
+ def test_CALL_aset
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for nil:NilClass
+
+ nil.[]=
+ ^^^^
+ END
+
+ nil.[]=
+ end
+ end
+
+ def test_CALL_op_asgn
+ v = nil
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ v += 42
+ ^
+ END
+
+ v += 42
+ end
+ end
+
+ def test_CALL_special_call_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `call' for nil:NilClass
+ END
+
+ nil.()
+ end
+ end
+
+ def test_CALL_special_call_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `call' for nil:NilClass
+ END
+
+ nil.(42)
+ end
+ end
+
+ def test_CALL_send
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.send(:foo, 42)
+ ^^^^^
+ END
+
+ nil.send(:foo, 42)
+ end
+ end
+
+ def test_ATTRASGN_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for nil:NilClass
+
+ nil [ ] = 42
+ ^^^^^
+ END
+
+ nil [ ] = 42
+ end
+ end
+
+ def test_ATTRASGN_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for nil:NilClass
+
+ nil [0] = 42
+ ^^^^^
+ END
+
+ nil [0] = 42
+ end
+ end
+
+ def test_ATTRASGN_3
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo=' for nil:NilClass
+
+ nil.foo = 42
+ ^^^^^^
+ END
+
+ nil.foo = 42
+ end
+ end
+
+ def test_OPCALL_binary_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ nil + 42
+ ^
+ END
+
+ nil + 42
+ end
+ end
+
+ def test_OPCALL_binary_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ nil + # comment
+ ^
+ END
+
+ nil + # comment
+ 42
+ end
+ end
+
+ def test_OPCALL_unary
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+@' for nil:NilClass
+
+ + nil
+ ^
+ END
+
+ + nil
+ end
+ end
+
+ def test_FCALL_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.instance_eval { foo() }
+ ^^^
+ END
+
+ nil.instance_eval { foo() }
+ end
+ end
+
+ def test_FCALL_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ nil.instance_eval { foo(42) }
+ ^^^
+ END
+
+ nil.instance_eval { foo(42) }
+ end
+ end
+
+ def test_VCALL_2
+ assert_error_message(NameError, <<~END) do
+undefined local variable or method `foo' for nil:NilClass
+
+ nil.instance_eval { foo }
+ ^^^
+ END
+
+ nil.instance_eval { foo }
+ end
+ end
+
+ def test_OP_ASGN1_aref_1
+ v = nil
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+
+ v [0] += 42
+ ^^^
+ END
+
+ v [0] += 42
+ end
+ end
+
+ def test_OP_ASGN1_aref_2
+ v = nil
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+
+ v [0] += # comment
+ ^^^
+ END
+
+ v [0] += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN1_aref_3
+ v = nil
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]' for nil:NilClass
+ END
+
+ v [
+ 0
+ ] += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN1_op_1
+ v = Object.new
+ def v.[](x); nil; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ v [0] += 42
+ ^
+ END
+
+ v [0] += 42
+ end
+ end
+
+ def test_OP_ASGN1_op_2
+ v = Object.new
+ def v.[](x); nil; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ v [0 ] += # comment
+ ^
+ END
+
+ v [0 ] += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN1_op_3
+ v = Object.new
+ def v.[](x); nil; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+ END
+
+ v [
+ 0
+ ] +=
+ 42
+ end
+ end
+
+ def test_OP_ASGN1_aset_1
+ v = Object.new
+ def v.[](x); 1; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for #{ v.inspect }
+
+ v [0] += 42
+ ^^^^^^
+ END
+
+ v [0] += 42
+ end
+ end
+
+ def test_OP_ASGN1_aset_2
+ v = Object.new
+ def v.[](x); 1; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for #{ v.inspect }
+
+ v [0] += # comment
+ ^^^^^^
+ END
+
+ v [0] += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN1_aset_3
+ v = Object.new
+ def v.[](x); 1; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for #{ v.inspect }
+ END
+
+ v [
+ 0
+ ] +=
+ 42
+ end
+ end
+
+ def test_OP_ASGN2_read_1
+ v = nil
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ v.foo += 42
+ ^^^^
+ END
+
+ v.foo += 42
+ end
+ end
+
+ def test_OP_ASGN2_read_2
+ v = nil
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo' for nil:NilClass
+
+ v.foo += # comment
+ ^^^^
+ END
+
+ v.foo += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN2_op_1
+ v = Object.new
+ def v.foo; nil; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ v.foo += 42
+ ^
+ END
+
+ v.foo += 42
+ end
+ end
+
+ def test_OP_ASGN2_op_2
+ v = Object.new
+ def v.foo; nil; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ v.foo += # comment
+ ^
+ END
+
+ v.foo += # comment
+ 42
+ end
+ end
+
+ def test_OP_ASGN2_write_1
+ v = Object.new
+ def v.foo; 1; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo=' for #{ v.inspect }
+
+ v.foo += 42
+ ^^^^^^^
+ END
+
+ v.foo += 42
+ end
+ end
+
+ def test_OP_ASGN2_write_2
+ v = Object.new
+ def v.foo; 1; end
+
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `foo=' for #{ v.inspect }
+
+ v.foo += # comment
+ ^^^^^^^
+ END
+
+ v.foo += # comment
+ 42
+ end
+ end
+
+ def test_CONST
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::NotDefined
+
+ 1 + NotDefined + 1
+ ^^^^^^^^^^
+ END
+
+ 1 + NotDefined + 1
+ end
+ end
+
+ def test_COLON2_1
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::NotDefined
+
+ ErrorSquiggleTest::NotDefined
+ ^^^^^^^^^^^^
+ END
+
+ ErrorSquiggleTest::NotDefined
+ end
+ end
+
+ def test_COLON2_2
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::NotDefined
+
+ NotDefined
+ ^^^^^^^^^^
+ END
+
+ ErrorSquiggleTest::
+ NotDefined
+ end
+ end
+
+ def test_COLON3
+ assert_error_message(NameError, <<~END) do
+uninitialized constant NotDefined
+
+ ::NotDefined
+ ^^^^^^^^^^^^
+ END
+
+ ::NotDefined
+ end
+ end
+
+ module OP_CDECL_TEST
+ Nil = nil
+ end
+
+ def test_OP_CDECL_read_1
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::OP_CDECL_TEST::NotDefined
+
+ OP_CDECL_TEST::NotDefined += 1
+ ^^^^^^^^^^^^
+ END
+
+ OP_CDECL_TEST::NotDefined += 1
+ end
+ end
+
+ def test_OP_CDECL_read_2
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::OP_CDECL_TEST::NotDefined
+
+ OP_CDECL_TEST::NotDefined += # comment
+ ^^^^^^^^^^^^
+ END
+
+ OP_CDECL_TEST::NotDefined += # comment
+ 1
+ end
+ end
+
+ def test_OP_CDECL_read_3
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::OP_CDECL_TEST::NotDefined
+ END
+
+ OP_CDECL_TEST::
+ NotDefined += 1
+ end
+ end
+
+ def test_OP_CDECL_op_1
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ OP_CDECL_TEST::Nil += 1
+ ^
+ END
+
+ OP_CDECL_TEST::Nil += 1
+ end
+ end
+
+ def test_OP_CDECL_op_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ OP_CDECL_TEST::Nil += # comment
+ ^
+ END
+
+ OP_CDECL_TEST::Nil += # comment
+ 1
+ end
+ end
+
+ def test_OP_CDECL_op_3
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for nil:NilClass
+
+ Nil += 1
+ ^
+ END
+
+ OP_CDECL_TEST::
+ Nil += 1
+ end
+ end
+
+ def test_OP_CDECL_toplevel_1
+ assert_error_message(NameError, <<~END) do
+uninitialized constant NotDefined
+
+ ::NotDefined += 1
+ ^^^^^^^^^^^^
+ END
+
+ ::NotDefined += 1
+ end
+ end
+
+ def test_OP_CDECL_toplevel_2
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `+' for ErrorSquiggleTest:Class
+
+ ::ErrorSquiggleTest += 1
+ ^
+ END
+
+ ::ErrorSquiggleTest += 1
+ end
+ end
+
+ def test_explicit_raise_name_error
+ assert_error_message(NameError, <<~END) do
+NameError
+
+ raise NameError
+ ^^^^^
+ END
+
+ raise NameError
+ end
+ end
+
+ def test_explicit_raise_no_method_error
+ assert_error_message(NoMethodError, <<~END) do
+NoMethodError
+
+ raise NoMethodError
+ ^^^^^
+ END
+
+ raise NoMethodError
+ end
+ end
+
+ def test_const_get
+ assert_error_message(NameError, <<~END) do
+uninitialized constant ErrorSquiggleTest::NotDefined
+
+ ErrorSquiggleTest.const_get(:NotDefined)
+ ^^^^^^^^^^
+ END
+
+ ErrorSquiggleTest.const_get(:NotDefined)
+ end
+ end
+
+ def test_local_variable_get
+ b = binding
+ assert_error_message(NameError, <<~END) do
+local variable `foo' is not defined for #{ b.inspect }
+
+ b.local_variable_get(:foo)
+ ^^^^^^^^^^^^^^^^^^^
+ END
+
+ b.local_variable_get(:foo)
+ end
+ end
+
+ def test_multibyte
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `あいうえお' for nil:NilClass
+
+ nil.あいうえお
+ ^^^^^^
+ END
+
+ nil.あいうえお
+ end
+ end
+
+ if false
+
+ def test_args_CALL_1
+ assert_error_message(TypeError, <<~END) do
+nil can't be coerced into Integer
+
+ 1.+(nil)
+ ^^^
+ END
+
+ 1.+(nil)
+ end
+ end
+
+ def test_args_CALL_2
+ v = []
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion from nil to integer
+
+ v[nil]
+ ^^^
+ END
+
+ v[nil]
+ end
+ end
+
+ def test_args_ATTRASGN_1
+ v = []
+ assert_error_message(ArgumentError, <<~END) do
+wrong number of arguments (given 1, expected 2..3)
+
+ v [ ] = 1
+ ^^^^^^
+ END
+
+ v [ ] = 1
+ end
+ end
+
+ def test_args_ATTRASGN_2
+ v = []
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion from nil to integer
+
+ v [nil] = 1
+ ^^^^^^^^
+ END
+
+ v [nil] = 1
+ end
+ end
+
+ def test_args_ATTRASGN_3
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion of String into Integer
+
+ $stdin.lineno = "str"
+ ^^^^^
+ END
+
+ $stdin.lineno = "str"
+ end
+ end
+
+ def test_args_OPCALL
+ assert_error_message(TypeError, <<~END) do
+nil can't be coerced into Integer
+
+ 1 + nil
+ ^^^
+ END
+
+ 1 + nil
+ end
+ end
+
+ def test_args_FCALL_1
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion of Symbol into String
+
+ "str".instance_eval { gsub("foo", :sym) }
+ ^^^^^^^^^^^
+ END
+
+ "str".instance_eval { gsub("foo", :sym) }
+ end
+ end
+
+ def test_args_FCALL_2
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion of Symbol into String
+
+ "str".instance_eval { gsub "foo", :sym }
+ ^^^^^^^^^^^
+ END
+
+ "str".instance_eval { gsub "foo", :sym }
+ end
+ end
+
+ def test_args_OP_ASGN1_aref_1
+ v = []
+
+ assert_error_message(TypeError, <<~END) do
+no implicit conversion from nil to integer
+
+ v [nil] += 42
+ ^^^^^^^^^^
+ END
+
+ v [nil] += 42
+ end
+ end
+
+ def test_args_OP_ASGN1_aref_2
+ v = []
+
+ assert_error_message(ArgumentError, <<~END) do
+wrong number of arguments (given 0, expected 1..2)
+
+ v [ ] += 42
+ ^^^^^^^^
+ END
+
+ v [ ] += 42
+ end
+ end
+
+ def test_args_OP_ASGN1_op
+ v = [1]
+
+ assert_error_message(TypeError, <<~END) do
+nil can't be coerced into Integer
+
+ v [0] += nil
+ ^^^^^^^^^
+ END
+
+ v [0] += nil
+ end
+ end
+
+ def test_args_OP_ASGN2
+ v = Object.new
+ def v.foo; 1; end
+
+ assert_error_message(TypeError, <<~END) do
+nil can't be coerced into Integer
+
+ v.foo += nil
+ ^^^
+ END
+
+ v.foo += nil
+ end
+ end
+
+ end
+end
diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb
index 5c48a8d853..7f100b7873 100644
--- a/test/ruby/marshaltestlib.rb
+++ b/test/ruby/marshaltestlib.rb
@@ -112,7 +112,7 @@ module MarshalTestLib
marshal_equal(Exception.new('foo')) {|o| o.message}
obj = Object.new
e = assert_raise(NoMethodError) {obj.no_such_method()}
- marshal_equal(e) {|o| o.message}
+ marshal_equal(e) {|o| o.message.lines.first.chomp}
end
def test_exception_subclass
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index d2b4ec169f..5e6891a6a0 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -817,7 +817,7 @@ class TestMarshal < Test::Unit::TestCase
nameerror_test
rescue NameError => e
e2 = Marshal.load(Marshal.dump(e))
- assert_equal(e.message, e2.message)
+ assert_equal(e.message.lines.first.chomp, e2.message.lines.first)
assert_equal(e.name, e2.name)
assert_equal(e.backtrace, e2.backtrace)
assert_nil(e2.backtrace_locations) # temporal
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 84e74693aa..3411c3d701 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -267,7 +267,7 @@ class TestModule < Test::Unit::TestCase
].each do |name, msg|
expected = "wrong constant name %s" % name
msg = "#{msg}#{': ' if msg}wrong constant name #{name.dump}"
- assert_raise_with_message(NameError, expected, "#{msg} to #{m}") do
+ assert_raise_with_message(NameError, Regexp.compile(Regexp.quote(expected)), "#{msg} to #{m}") do
yield name
end
end
diff --git a/test/ruby/test_name_error.rb b/test/ruby/test_name_error.rb
index 813a976e96..8fcc2dcb26 100644
--- a/test/ruby/test_name_error.rb
+++ b/test/ruby/test_name_error.rb
@@ -151,6 +151,6 @@ class TestNameError < Test::Unit::TestCase
error = assert_raise(NameError) do
receiver::FOO
end
- assert_equal "uninitialized constant #{'A' * 120}::FOO", error.message
+ assert_match /\Auninitialized constant #{'A' * 120}::FOO$/, error.message
end
end
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb
index 8b81052905..321b7ccab2 100644
--- a/test/ruby/test_nomethod_error.rb
+++ b/test/ruby/test_nomethod_error.rb
@@ -86,7 +86,7 @@ class TestNoMethodError < Test::Unit::TestCase
str = "\u2600"
id = :"\u2604"
msg = "undefined method `#{id}' for \"#{str}\":String"
- assert_raise_with_message(NoMethodError, msg, bug3237) do
+ assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do
str.__send__(id)
end
end
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 774b707742..83208bbcdb 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -777,7 +777,7 @@ class TestObject < Test::Unit::TestCase
e = assert_raise(NoMethodError) {
o.never_defined_test_no_superclass_method
}
- assert_equal(m1, e.message, bug2312)
+ assert_equal(m1.lines.first, e.message.lines.first, bug2312)
end
def test_superclass_method