Repository: natalie-lang/natalie (Ruby)

Issue:
Evaluation order of receiver and arguments is backwards
One of the leftovers of #862, where this code caused issues:
```ruby
(set = Set[]) << set
```
This raises an error (backtrace from commit 490d07224b66a98db6fb297ab475204a73ceb54a):
```
lib/natalie/compiler/instructions/variable_get_instruction.rb:48:in `generate': variable not defined set (RuntimeError)
	from lib/natalie/compiler/backends/cpp_backend/transform.rb:31:in `block in transform'
	from lib/natalie/compiler/instruction_manager.rb:27:in `walk'
	from lib/natalie/compiler/backends/cpp_backend/transform.rb:29:in `transform'
	from lib/natalie/compiler/backends/cpp_backend.rb:33:in `transform_instructions'
	from lib/natalie/compiler/backends/cpp_backend.rb:18:in `generate'
	from lib/natalie/compiler.rb:224:in `transform'
	from lib/natalie/compiler.rb:143:in `instructions'
	from lib/natalie/compiler.rb:102:in `write_file'
	from lib/natalie/compiler.rb:81:in `compile'
	from bin/natalie:139:in `run'
	from bin/natalie:305:in `<main>'
```
A few more ways to trigger this error (which all work in MRI)
```ruby
(a = 1) + a
(a = "") +  a
```
The following comparable statements have the same behaviour in MRI and Natalie:
```ruby
a = a # a => nil
a = "" + a # raises TypeError with message "no implicit conversion from nil to string"
a = "foo" + a.to_s # a => "foo", because a is nil before the assignment
```
I think the last one is the most interesting of the bunch, since adding parentheses causes Natalie to break again, which probably is the minimal change to trigger this error
```ruby
(a = "foo") + a.to_s
```
If the variable exists, the statement works, and the operations are done in correct order
```ruby
a = nil
(a = 1) + a # a => 1, return value => 2
```


Discussion:
Once this is fixed, the change in `spec/library/set/shared/inspect.rb` can be reverted.
The issue here is actually that the "receiver" of a call/message and the arguments are not evaluated in the expected order. It's surprising to me that we haven't noticed this before!

Here is a failing test for that:

```ruby
describe 'call order' do
  @order = []

  def first
    @order << 1
    1
  end

  def second
    @order << 2
    2
  end

  it 'evaluates the receiver before the arguments' do
    (first + second).should == 3
    @order.should == [1, 2]
  end
end
```



Patch:
diff --git a/lib/natalie/compiler/instructions/method_defined_instruction.rb b/lib/natalie/compiler/instructions/method_defined_instruction.rb
index 28d4ca9d62..b0b6614741 100644
--- a/lib/natalie/compiler/instructions/method_defined_instruction.rb
+++ b/lib/natalie/compiler/instructions/method_defined_instruction.rb
@@ -21,8 +21,6 @@ def to_s
       end
 
       def generate(transform)
-        receiver = transform.pop
-
         if @args_array_on_stack
           transform.pop
         else
@@ -30,6 +28,8 @@ def generate(transform)
           arg_count.times { transform.pop }
         end
 
+        receiver = transform.pop
+
         transform.exec(
           "if (!#{receiver}->respond_to(env, #{transform.intern(@message)})) throw new ExceptionObject"
         )
diff --git a/lib/natalie/compiler/instructions/move_rel_instruction.rb b/lib/natalie/compiler/instructions/move_rel_instruction.rb
index e8c3cbe3e7..2cda43c9be 100644
--- a/lib/natalie/compiler/instructions/move_rel_instruction.rb
+++ b/lib/natalie/compiler/instructions/move_rel_instruction.rb
@@ -18,7 +18,9 @@ def generate(transform)
       end
 
       def execute(vm)
-        raise 'todo'
+        raise 'ran out of stack' unless vm.stack.length > @index
+        value = vm.stack.slice!(vm.stack.length - 1 - @index)
+        vm.push(value)
       end
     end
   end
diff --git a/lib/natalie/compiler/instructions/send_instruction.rb b/lib/natalie/compiler/instructions/send_instruction.rb
index e0b156209e..95ba9dd36e 100644
--- a/lib/natalie/compiler/instructions/send_instruction.rb
+++ b/lib/natalie/compiler/instructions/send_instruction.rb
@@ -55,9 +55,6 @@ def to_s
       end
 
       def generate(transform)
-        receiver = transform.pop
-        raise "bad receiver #{receiver.inspect} for SendInstruction #{@message.inspect}" unless receiver.is_a?(String) || receiver.is_a?(String)
-
         if @args_array_on_stack
           if @forward_args
             args_list = transform.pop
@@ -76,6 +73,14 @@ def generate(transform)
         transform.set_file(@file)
         transform.set_line(@line)
 
+        receiver = transform.pop
+        unless receiver.is_a?(String)
+          puts "Something went wrong: bad receiver #{receiver.inspect} for SendInstruction #{@message.inspect}"
+          puts "Got arg_count = #{arg_count.inspect}"
+          puts "Got args = #{args.inspect}"
+          exit 1
+        end
+
         block = @with_block ? "to_block(env, #{transform.pop})" : 'nullptr'
         transform.exec_and_push(
           "send_#{@message}",
@@ -84,8 +89,6 @@ def generate(transform)
       end
 
       def execute(vm)
-        receiver = vm.pop
-
         if @args_array_on_stack
           args = vm.pop
         else
@@ -94,6 +97,8 @@ def execute(vm)
           arg_count.times { args.unshift vm.pop }
         end
 
+        receiver = vm.pop
+
         if args.empty? && %i[public private protected].include?(@message)
           vm.method_visibility = @message
           vm.push(receiver)
diff --git a/lib/natalie/compiler/instructions/super_instruction.rb b/lib/natalie/compiler/instructions/super_instruction.rb
index 98db4be1e6..2b8e5fe9d6 100644
--- a/lib/natalie/compiler/instructions/super_instruction.rb
+++ b/lib/natalie/compiler/instructions/super_instruction.rb
@@ -16,7 +16,6 @@ def to_s
       end
 
       def generate(transform)
-        receiver = transform.pop
         if @args_array_on_stack
           args = transform.pop
           arg_count = "#{args}->as_array()->size()"
@@ -29,6 +28,8 @@ def generate(transform)
           transform.exec "Value #{args_array_on_stack}[] = { #{args.join(', ')} };"
         end
 
+        receiver = transform.pop
+
         # NOTE: There is a similar line in SendInstruction#generate, but
         # this one differs in that we fall back to sending the `block`
         # from the current method, if it is set.
@@ -39,7 +40,6 @@ def generate(transform)
       end
 
       def execute(vm)
-        receiver = vm.pop
         args = if @args_array_on_stack
                  vm.pop
                else
@@ -48,6 +48,7 @@ def execute(vm)
                  arg_count.times { args.unshift vm.pop }
                  args
                end
+        receiver = vm.pop
         block = @with_block ? vm.pop : vm.block
         vm.with_self(receiver) do
           result = receiver.method(vm.method_name).super_method.call(*args, &block)
diff --git a/lib/natalie/compiler/multiple_assignment.rb b/lib/natalie/compiler/multiple_assignment.rb
index cd2403002f..1cbaf44646 100644
--- a/lib/natalie/compiler/multiple_assignment.rb
+++ b/lib/natalie/compiler/multiple_assignment.rb
@@ -54,13 +54,19 @@ def transform_arg(arg)
 
       def transform_attr_assign_arg(receiver, message, args)
         shift_or_pop_next_arg
+        @instructions << @pass.transform_expression(receiver, used: true)
         if args.any?
           args.each { |arg| @instructions << @pass.transform_expression(arg, used: true) }
-          @instructions << MoveRelInstruction.new(args.size) # move value after args
         end
+        @instructions << MoveRelInstruction.new(args.size + 1) # move value after args
         @instructions << PushArgcInstruction.new(args.size + 1)
-        @instructions << @pass.transform_expression(receiver, used: true)
-        @instructions << SendInstruction.new(message, receiver_is_self: false, with_block: false, file: @file, line: @line)
+        @instructions << SendInstruction.new(
+          message,
+          receiver_is_self: false,
+          with_block: false,
+          file: @file,
+          line: @line,
+        )
         @instructions << PopInstruction.new
       end
 
@@ -91,9 +97,16 @@ def transform_splat_arg(arg)
           @instructions << VariableGetInstruction.new(name)
         when :attrasgn
           _, receiver, message = arg
-          @instructions << PushArgcInstruction.new(1)
           @instructions << @pass.transform_expression(receiver, used: true)
-          @instructions << SendInstruction.new(message, receiver_is_self: false, with_block: false, file: @file, line: @line)
+          @instructions << SwapInstruction.new
+          @instructions << PushArgcInstruction.new(1)
+          @instructions << SendInstruction.new(
+            message,
+            receiver_is_self: false,
+            with_block: false,
+            file: @file,
+            line: @line,
+          )
         else
           raise "I don't yet know how to compile splat arg #{arg.inspect}"
         end
diff --git a/lib/natalie/compiler/pass1.rb b/lib/natalie/compiler/pass1.rb
index 820c57b2e3..e6fb7659bc 100644
--- a/lib/natalie/compiler/pass1.rb
+++ b/lib/natalie/compiler/pass1.rb
@@ -161,8 +161,13 @@ def transform_nth_ref(exp, used:)
           IfInstruction.new,
           PushIntInstruction.new(num),
           PushArgcInstruction.new(1),
-          DupRelInstruction.new(2),
-          SendInstruction.new(:[], receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+          SendInstruction.new(
+            :[],
+            receiver_is_self: false,
+            with_block: false,
+            file: exp.file,
+            line: exp.line,
+          ),
           ElseInstruction.new(:if),
           PopInstruction.new,
           PushNilInstruction.new,
@@ -235,16 +240,16 @@ def transform_call(exp, used:, with_block: false)
           ]
         end
 
-        call_args = transform_call_args(args)
-        instructions = call_args.fetch(:instructions)
-        with_block ||= call_args.fetch(:with_block_pass)
-
+        instructions = []
         if receiver.nil?
           instructions << PushSelfInstruction.new
         else
           instructions << transform_expression(receiver, used: true)
         end
 
+        call_args = transform_call_args(args, instructions: instructions)
+        with_block ||= call_args.fetch(:with_block_pass)
+
         instructions << SendInstruction.new(
           message,
           args_array_on_stack: call_args.fetch(:args_array_on_stack),
@@ -259,18 +264,15 @@ def transform_call(exp, used:, with_block: false)
         instructions
       end
 
-      def transform_call_args(args)
-        instructions = []
-
+      def transform_call_args(args, instructions: [])
         if args.last&.sexp_type == :block_pass
           _, block = args.pop
-          instructions << transform_expression(block, used: true)
+          instructions.unshift(transform_expression(block, used: true))
         end
 
         if args.any? { |a| a.sexp_type == :splat }
           instructions << transform_array_with_splat(args)
           return {
-            instructions: instructions,
             with_block_pass: !!block,
             args_array_on_stack: true,
             has_keyword_hash: args.last&.sexp_type == :bare_hash
@@ -641,9 +643,15 @@ def transform_dstr(exp, used:)
 
       def transform_dsym(exp, used:)
         instructions = [
-          PushArgcInstruction.new(0),
           transform_dstr(exp, used: true),
-          SendInstruction.new(:to_sym, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+          PushArgcInstruction.new(0),
+          SendInstruction.new(
+            :to_sym,
+            receiver_is_self: false,
+            with_block: false,
+            file: exp.file,
+            line: exp.line,
+          ),
         ]
         instructions << PopInstruction.new unless used
         instructions
@@ -1010,18 +1018,34 @@ def transform_op_asgn1(exp, used:)
         _, obj, (_, *key_args), op, value = exp
         if op == :'||'
           instructions = [
+            # stack: [obj]
+            transform_expression(obj, used: true),
+
+            # stack: [obj, *keys]
             key_args.map { |arg| transform_expression(arg, used: true) },
 
-            # key(s) are reused when the value is set
-            key_args.each_with_index.map { |_, index| DupRelInstruction.new(index) },
+            # stack: [obj, *keys, obj]
+            DupRelInstruction.new(key_args.size),
 
-            # old_value = obj[key]
+            # stack: [obj, *keys, obj, *keys]
+            key_args.each_with_index.map { |_, index| DupRelInstruction.new(index + key_args.size) },
+
+            # old_value = obj[*keys]
+            # stack: [obj, *keys, old_value]
             PushArgcInstruction.new(key_args.size),
-            transform_expression(obj, used: true),
-            SendInstruction.new(:[], receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+            SendInstruction.new(
+              :[],
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
+
+            # stack: [obj, *keys, old_value, old_value]
             DupInstruction.new,
 
             # if old_value
+            # stack: [obj, *keys, old_value]
             IfInstruction.new,
 
             # didn't need the extra key(s) after all :-)
@@ -1034,38 +1058,72 @@ def transform_op_asgn1(exp, used:)
 
             ElseInstruction.new(:if),
 
-            # obj[key] = new_value
+            # stack: [obj, *keys]
             PopInstruction.new,
+
+            # obj[*keys] = new_value
+            # stack: [obj, *keys, new_value]
             transform_expression(value, used: true),
             PushArgcInstruction.new(key_args.size + 1),
-            transform_expression(obj, used: true),
-            SendInstruction.new(:[]=, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+            SendInstruction.new(
+              :[]=,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
 
             EndInstruction.new(:if),
           ]
         else
           instructions = [
             # old_value = obj[key]
+            # stack: [obj]
             transform_expression(obj, used: true),
+
+            # stack: [obj, *keys]
             key_args.map { |arg| transform_expression(arg, used: true) },
 
-            # key(s) are reused when the value is set
-            key_args.each_with_index.map { |_, index| DupRelInstruction.new(index) },
+            # stack: [obj, *keys, obj]
+            DupRelInstruction.new(key_args.size),
+
+            # stack: [obj, *keys, obj, *keys]
+            key_args.each_with_index.map { |_, index| DupRelInstruction.new(index + key_args.size) },
 
+            # old_value = obj[*keys]
+            # stack: [obj, *keys, old_value]
             PushArgcInstruction.new(key_args.size),
-            DupRelInstruction.new(key_args.size * 2 + 1),
-            SendInstruction.new(:[], receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+            SendInstruction.new(
+              :[],
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
+
+            # stack: [obj, *keys, old_value, value]
+            transform_expression(value, used: true),
 
             # new_value = old_value + value
-            transform_expression(value, used: true),
+            # stack: [obj, *keys, new_value]
             PushArgcInstruction.new(1),
-            MoveRelInstruction.new(2),
-            SendInstruction.new(op, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
-
-            # obj[key] = new_value
+            SendInstruction.new(
+              op,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
+
+            # obj[*keys] = new_value
             PushArgcInstruction.new(key_args.size + 1),
-            MoveRelInstruction.new(key_args.size + 2),
-            SendInstruction.new(:[]=, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+            SendInstruction.new(
+              :[]=,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
           ]
         end
         instructions << PopInstruction.new unless used
@@ -1079,28 +1137,51 @@ def transform_op_asgn2(exp, used:)
         if op == :'||'
           raise 'todo'
         else
-          # given: obj.foo += 2
+          # given: obj.foo += value
           instructions = [
-            # obj
+            # stack: [obj]
             transform_expression(obj, used: true),
 
-            # 2
+            # stack: [obj, *values]
             val_args.map { |arg| transform_expression(arg, used: true) },
 
+            # stack: [obj, *values, obj]
+            DupRelInstruction.new(val_args.size),
+
             # temp = obj.foo
+            # stack: [obj, *values, temp]
             PushArgcInstruction.new(0),
-            DupRelInstruction.new(2),
-            SendInstruction.new(reader, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
-
-            # result = temp + 2
+            SendInstruction.new(
+              reader,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
+
+            # stack: [obj, temp, *values]
+            val_args.map { |arg| MoveRelInstruction.new(val_args.size) },
+
+            # result = temp + value
+            # stack: [obj, new_value]
             PushArgcInstruction.new(val_args.size),
-            SwapInstruction.new,
-            SendInstruction.new(op, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
-
-            # obj.foo = result
+            SendInstruction.new(
+              op,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
+
+            # obj.foo = new_value
             PushArgcInstruction.new(1),
-            DupRelInstruction.new(2),
-            SendInstruction.new(writer, receiver_is_self: false, with_block: false, file: exp.file, line: exp.line),
+            SendInstruction.new(
+              writer,
+              receiver_is_self: false,
+              with_block: false,
+              file: exp.file,
+              line: exp.line,
+            ),
           ]
           instructions << PopInstruction.new unless used
           instructions
@@ -1150,15 +1231,18 @@ def transform_return(exp, used:) # rubocop:disable Lint/UnusedMethodArgument
       def transform_safe_call(exp, used:)
         _, receiver, message, *args = exp
 
-        call_args = transform_call_args(args)
-        instructions = call_args.fetch(:instructions)
-
+        instructions = []
         instructions << transform_expression(receiver, used: true)
-        instructions << DupInstruction.new
+
+        instructions << DupInstruction.new # duplicate receiver for IsNil below
         instructions << IsNilInstruction.new
         instructions << IfInstruction.new
+        instructions << PopInstruction.new # pop duplicated receiver since it is unused
         instructions << PushNilInstruction.new
         instructions << ElseInstruction.new(:if)
+
+        call_args = transform_call_args(args, instructions: instructions)
+
         instructions << SendInstruction.new(
           message,
           args_array_on_stack: call_args.fetch(:args_array_on_stack),
@@ -1203,9 +1287,9 @@ def transform_str(exp, used:)
 
       def transform_super(exp, used:, with_block: false)
         _, *args = exp
-        call_args = transform_call_args(args)
-        instructions = call_args.fetch(:instructions)
+        instructions = []
         instructions << PushSelfInstruction.new
+        call_args = transform_call_args(args, instructions: instructions)
         instructions << SuperInstruction.new(
           args_array_on_stack: call_args.fetch(:args_array_on_stack),
           with_block: with_block || call_args.fetch(:with_block_pass),
@@ -1232,8 +1316,9 @@ def transform_svalue(exp, used:)
 
       def transform_to_ary(exp, used:)
         _, value = exp
-        instructions = [PushArgcInstruction.new(0)]
+        instructions = []
         instructions << transform_expression(value, used: true)
+        instructions << PushArgcInstruction.new(0)
         instructions << SendInstruction.new(
           :to_ary,
           receiver_is_self: false,
@@ -1292,8 +1377,8 @@ def transform_xstr(exp, used:)
 
       def transform_yield(exp, used:)
         _, *args = exp
-        call_args = transform_call_args(args)
-        instructions = call_args.fetch(:instructions)
+        instructions = []
+        call_args = transform_call_args(args, instructions: instructions)
         instructions << YieldInstruction.new(
           args_array_on_stack: call_args.fetch(:args_array_on_stack),
         )
@@ -1303,8 +1388,8 @@ def transform_yield(exp, used:)
 
       def transform_zsuper(_, used:, with_block: false)
         instructions = []
-        instructions << PushArgsInstruction.new(for_block: false, min_count: 0, max_count: 0)
         instructions << PushSelfInstruction.new
+        instructions << PushArgsInstruction.new(for_block: false, min_count: 0, max_count: 0)
         instructions << SuperInstruction.new(
           args_array_on_stack: true,
           with_block: with_block,
diff --git a/lib/natalie/compiler/pass3.rb b/lib/natalie/compiler/pass3.rb
index 140dbdd1c3..9d8e71cdf9 100644
--- a/lib/natalie/compiler/pass3.rb
+++ b/lib/natalie/compiler/pass3.rb
@@ -80,8 +80,8 @@ def transform_send(instruction)
           CatchInstruction.new,
           MatchBreakPointInstruction.new(break_point),
           IfInstruction.new,
-          PushArgcInstruction.new(0),
           GlobalVariableGetInstruction.new(:$!),
+          PushArgcInstruction.new(0),
           SendInstruction.new(
             :exit_value,
             receiver_is_self: false,
@@ -90,8 +90,8 @@ def transform_send(instruction)
             line: instruction.line,
           ),
           ElseInstruction.new(:if),
-          PushArgcInstruction.new(0),
           PushSelfInstruction.new,
+          PushArgcInstruction.new(0),
           SendInstruction.new(
             :raise,
             receiver_is_self: true,
diff --git a/lib/natalie/compiler/pass4.rb b/lib/natalie/compiler/pass4.rb
index 198db0231b..b66810b795 100644
--- a/lib/natalie/compiler/pass4.rb
+++ b/lib/natalie/compiler/pass4.rb
@@ -84,8 +84,8 @@ def transform_send(instruction)
           CatchInstruction.new,
           MatchBreakPointInstruction.new(break_point),
           IfInstruction.new,
-          PushArgcInstruction.new(0),
           GlobalVariableGetInstruction.new(:$!),
+          PushArgcInstruction.new(0),
           SendInstruction.new(
             :exit_value,
             receiver_is_self: false,
@@ -95,8 +95,8 @@ def transform_send(instruction)
           ),
           ReturnInstruction.new,
           ElseInstruction.new(:if),
-          PushArgcInstruction.new(0),
           PushSelfInstruction.new,
+          PushArgcInstruction.new(0),
           SendInstruction.new(
             :raise,
             receiver_is_self: true,


Test patch:
diff --git a/test/natalie/call_order_test.rb b/test/natalie/call_order_test.rb
new file mode 100644
index 0000000000..97383386d2
--- /dev/null
+++ b/test/natalie/call_order_test.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+
+describe 'call order test' do
+  @order = []
+
+  def first
+    @order << 1
+    1
+  end
+
+  def second
+    @order << 2
+    2
+  end
+
+  it 'evaluates the receiver before the arguments' do
+    (first + second).should == 3
+    @order.should == [1, 2]
+  end
+
+  describe 'assignment' do
+    it 'can be assigned and mutated in a single expression' do
+      ((l = 1) + l).should == 2
+      ((@i = 2) + @i).should == 4
+      (($g = 3) + $g).should == 6
+    end
+  end
+end
diff --git a/test/support/spec.rb b/test/support/spec.rb
index 601e61f8ee..c58ef9805c 100644
--- a/test/support/spec.rb
+++ b/test/support/spec.rb
@@ -894,6 +894,17 @@ def reset
 
     @stubs.clear
   end
+
+  def snapshot
+    stubs_before = @stubs.dup
+    yield
+  ensure
+    stub_keys_added = @stubs.keys - stubs_before.keys
+    stub_keys_added.each do |key|
+      @stubs[key].each(&:disable)
+    end
+    @stubs = stubs_before
+  end
 end
 
 class Stub
@@ -1009,12 +1020,13 @@ def any_number_of_times
     self
   end
 
+  alias disable any_number_of_times
+
   def validate!
     unless @count_restriction == nil || @count_restriction === @count
-      message = "#{@subject.inspect} should have received ##{@message}"
-      if @count_restriction != nil
-        message << " #{@count_restriction} time(s) but received #{@count} time(s)"
-      end
+      actual_count = @count
+      message = "#{@subject.inspect} should have received ##{@message} " \
+                "#{@count_restriction} time(s) but received #{actual_count} time(s)"
 
       raise SpecFailedException, message
     end
@@ -1463,7 +1475,13 @@ def NATFIXME(description, exception: nil, message: nil)
   end
 
   status, ex = begin
-    yield
+    if $stub_registry
+      $stub_registry.snapshot do
+        yield
+      end
+    else
+      yield
+    end
     [:unexpected_pass, nil]
   rescue exception => ex
     if matcher.call(ex.message)
@@ -1479,7 +1497,7 @@ def NATFIXME(description, exception: nil, message: nil)
   when :unexpected_pass
     raise NatalieFixMeException, "Issue has been fixed, please remove or update the NATFIXME marker"
   when :correct_error_class_wrong_message
-    raise NatalieFixMeException, "Issue hidden by NATFIXME marker message is incorrect"
+    raise NatalieFixMeException, "Issue hidden by NATFIXME marker message is incorrect (should be #{message.inspect} but was #{ex.message.inspect})"
   when :wrong_error_class
     raise NatalieFixMeException, "Issue hidden by NATFIXME marker class is incorrect.  Expected #{exception}, was #{ex.class}"
   end