TL; DR
This is truly interpreter specific. The problem manifests itself in Ruby 2.1.2 MRI and JRuby 1.7.13, but it works as expected in Rubinius . For example, with Rubinius 2.2.10:
x = true z = y if y = x #=> true
In the MRI, a small study with Ripper shows that Ruby treats the post-condition differently, even if the assignment ASTs are similar. It actually uses different tokens for post-conditions when constructing the AST, and this, apparently, affects the order of evaluation of assignment expressions. Whether this will be so or not, or it can be fixed, is a question for the Ruby Core Team .
Why does this work with logical and
x = true y = x and z = y
This is done because there are indeed two assignments in the sequence, because true assigned to x and therefore evaluates to true. Since the first expression is true, the next expression is logical, and is also evaluated and also evaluated as true.
y = x #=> true z = y #=> true
In other words, x is set to true , and then z is also set to true . In no case is the right side of any assignment undefined.
Why does this happen with the post-condition
x = true z = y if y = x
In this case, the precondition is satisfied first. You can see this by looking at AST :
require 'pp' require 'ripper' x = true pp Ripper.sexp 'z = y if y = x' [:program, [[:if_mod, [:assign, [:var_field, [:@ident, "y", [1, 9]]], [:vcall, [:@ident, "x", [1, 13]]]], [:assign, [:var_field, [:@ident, "z", [1, 0]]], [:vcall, [:@ident, "y", [1, 4]]]]]]]
Unlike your first example, where y was assigned true in the first expression and therefore allowed to true in the second expression before being assigned to z, in this case y is calculated as yet undefined. This raises a NameError .
Of course, it could legitimately be argued that both expressions contain assignments, and that y will not be truly undefined if Ruby parsing evaluates y = x first, as happens with the normal if statement (see AST below). This is probably just a fad of postoperative if statements and the way Ruby processes the token: if_mod.
Succeed With: if Instead: if_mod Tokens
If you cancel the logic and use the normal if statement, it works fine:
x = true if y = x z = y end #=> true
A look at Ripper gives the following AST:
require 'pp' require 'ripper' x = true pp Ripper.sexp 'if y = x; z = y; end' [:program, [[:if, [:assign, [:var_field, [:@ident, "y", [1, 3]]], [:vcall, [:@ident, "x", [1, 7]]]], [[:assign, [:var_field, [:@ident, "z", [1, 10]]], [:var_ref, [:@ident, "y", [1, 14]]]]], nil]]]
Note that the only real difference is that the example that throws NameError uses: if_mod, and the version that succeeds uses: if. The post-condition seems to be causing the error, quirk, or error you see.
What to do with her
There may be a good technical reason for this parsing behavior, or not. I can not judge. However, if this seems like a mistake and you are motivated to do something, the best thing would be to check the Ruby Issue Tracker to see if it has already been reported. If not, maybe the time has come when someone officially raised it.