An assignment behind if false still shadows your method
Question
What is the return value of the following Ruby code?
class Person
def firstname = "Jon"
def lastname = "Jones"
def fullname
lastname = "Snow" if false
"#{firstname} #{lastname}"
end
end
Person.new.fullname # => ???
The correct answer is
-
"Jon Jones" -
"Jon Snow" -
"Jon "Correct -
It raises
NameError
Explanation
TL;DR
The parser registers lastname as a local variable the moment it reads the assignment lastname = "Snow", even though the if false guard means the assignment never executes. From that line onward, the bare word lastname refers to that local variable, which holds its default value of nil, not to the Person#lastname method. firstname is still a method call, so the interpolation produces "Jon ".
Disasm
To see the mechanism, reference lastname both before and after the assignment, then disassemble the method:
class Person
def lastname = "Jones"
def fullname
lastname
lastname = "Snow" if false
lastname
end
end
puts RubyVM::InstructionSequence.disasm(Person.new.method(:fullname))
Output (the == disasm header line is trimmed):
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] lastname@0
0000 putself ( 5)[LiCa]
0001 opt_send_without_block <calldata!mid:lastname, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 pop
0004 getlocal_WC_0 lastname@0 ( 7)[Li]
0006 leave ( 8)[Re]
The local table already contains a lastname@0 slot before a single instruction runs. The slot exists because the parser saw an assignment, not because one executed.
The reference on line 5, before the assignment, compiles to a method call:
opt_send_without_block <calldata!mid:lastname, argc:0, ...>
The reference on line 7, after the assignment, compiles to a local variable read:
getlocal_WC_0 lastname@0
Note that the if false branch itself was optimized away entirely, yet its assignment still changed how the later reference compiles.
The rule: local variables are created at parse time. Once an assignment to a name appears, every later bare reference to that name in the same scope reads the local variable, which defaults to nil when the assignment never ran. To reach the method anyway, force a method call with an explicit receiver or parentheses: self.lastname or lastname() both return "Jones".
Share this quiz
Comments
No comments yet. Be the first.