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:
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
:
(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:
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!
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!