by

Ruby: SyntaxError: void value expression

I recently came across this error in a Ruby project:

1SyntaxError: foo.rb:123: void value expression

After some digging and simplifying, I isolated the issue to something like the following:

1def void_assignment
2  a =
3    begin
4      return 1
5    end
6end

The method includes an assignment, but it is never realised: the code hits return and the execution ends, leaving the a sort of "dangling", not receiving its promised value. Ruby does not like this!

TLDR fix

Remove the useless return:

1def void_assignment
2  a =
3    begin
4      1
5    end
6end

(Sure, there is the question of why having an assignment at all, but this is just an extremely simplified example).

What is a "void value expression"?

Ruby says void value expression because a return… does not return anything, counterintuitively. Sure, it "returns" a value to the code that called the current method, but it does not return anything within the method where it lives. In other words, a return expression resolves to a void value.

So if you have this:

1a = return 1

Then a never gets a value: it is "void". Not even nil, which would be a valid value in Ruby.

Sometimes the parser detects this in advance of running the code, raising a syntax error and forcing you to fix it. It is a bit strange, because other times it cannot detect it, runs the code normally, and there are no runtime errors or anything. I am sure there is a good reason for declaring it an error, but I do not know it.

Variations

This error appears in other instances where you have a return and Ruby expects a value. For example:

1return true and false

This may look like it should be equivalent to return false, but the word and is subtly different from the operator &&. It resolves later than return, while && resolves before return. We say that and has "lower precedence" than return and && has "higher precedence".

You may remember, possibly from secondary school, how multiplications × had to be calculated before additions +. This is what "precedence" is about. Therefore the above code is equivalent to (return true) and false, where the parentheses make this precedence clearer, highlighting how false is never reached.

Other Ruby implementations

More interestingly, different Ruby implementations handle this differently. My team actually came across this issue working with JRuby instead of MRI. Have a look at the following method:

1def void_assignment_with_if
2  a =
3    begin
4      if true
5        return 1
6      end
7    end
8end

The code above parses and runs with MRI, but fails with the syntax error in JRuby. My guess is that JRuby's JIT compiler looks more closely into that sort of situation, detecting that the if is masking a potential void value expression.

But the following works!

 1def void_assignment_with_if
 2  a =
 3    begin
 4      if true
 5        return 1
 6      end
 7
 8      puts "AFTER"
 9    end
10end

I'm sure there's a perfectly reasonable explanation for all this, but I am not looking any deeper :-)

Why doesn't my linter detect this?

It surprised me that RuboCop (and by extension StandardRB) does not flag this as an issue. If at least this was a "normal" syntax error, RuboCop would not need a rule to detect it: its parser would probably throw an error while analysing the file (before showing actual linter warnings) and that would help. However this is a "post-parse" syntax error, so the parser that RuboCop uses is not troubled by it.

My impression is that this must be a relatively known scenario for people who know better than me, like the RuboCop maintainers. However I cannot see why there would not be a rule for it. Who knows, maybe I came across something "new".

But the best way to make sure is to ask people who know better. So I went and did this in the form of a tentative PR for RuboCop.

People are busy and this may take a while to be looked into (I will bump it a bit after some time), but ultimately I am hoping it will be enlightening, and perhaps offer a new RuboCop rule to everyone's benefit.

Watch this space for updates!