Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NameError: undefined - have parsing rules for local variables changed in Ruby 2.1.2?

I am getting NameError: undefined local variable or method with ruby 2.1.2

As observed in this question, expressions like:

bar if bar = true

raises an undefined local variable error (provided that bar is not defined prior) because bar is read by the parser before it is assigned. And I believe that there used to be no difference with that with this expression:

bar if bar = false

The difference between the two is whether the main body is evaluated or not, but that should not matter if encountering an undefined local variable immediately raises an error before evaluating the condition.

But when I run the second code on Ruby 2.1.2, it does not raise an error. Has it been like that from before? If so, then what was the parsing discussion all about? If not, has Ruby specification changed? Is there any reference to that? What did it do in 1.8.7, 1.9.3, etc. ?

like image 808
sawa Avatar asked Aug 14 '14 11:08

sawa


3 Answers

There is no difference with regards to whether bar is defined or not. In both cases, bar is undefined in the body. However, in the latter case, the body is never evaluated, therefore it doesn't matter. You never resolve the name bar, therefore you never get an error during name resolution.

Local variables are defined when an assignment is parsed. They are initialized when an assignment is executed.

It's perfectly fine for a variable to be unitialized. It will just evaluate to nil in that case:

if false
  bar = 42
end

bar
# => nil

However, if the variable is undefined, then Ruby doesn't know whether a bare word is a local variable or a receiverless argumentless message send:

foo
# NameError: undefined local variable or method `foo'
#                                     ^^^^^^^^^
# Ruby doesn't know whether it's a variable or a message send

Compare with:

foo()
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^

self.foo
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^

All together now:

foo()
# NoMethodError: undefined method `foo'

self.foo
# NoMethodError: undefined method `foo'

foo
# NameError: undefined local variable or method `foo'

if false
  foo = 42
end

foo
# => nil

foo = :fortytwo

foo
# => :fortytwo

The trouble in this particular case is that the order in which the expressions are parsed (and thus the order in which variables are defined) does not match with the order in which the expressions are executed.

The assignment is executed first, which would make you assume that bar would be defined in the body. But it isn't, because the body was parsed first and thus an I don't know whether this is a method or a variable node was inserted into the syntax tree before the assignment was ever seen.

However, if that node is never interpreted, i.e. the condition is false, then nothing bad will happen.

like image 86
Jörg W Mittag Avatar answered Nov 15 '22 03:11

Jörg W Mittag


Yes it changed in ruby 2.1.2

In 1.8.7, 1.9.3, 2.0.0 and even 2.1.1 I get 2 warnings and no errors:

2.0.0-p247 :007 > bar if bar = false
(irb):7: warning: found = in conditional, should be ==
 => nil 
2.0.0-p247 :008 > bar if bar = true
(irb):8: warning: found = in conditional, should be ==
 => true 

whereas in the 2.1.2 version you mention I get 2 warnings and 1 NameError error.

2.1.2 :001 > bar if bar = true
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method `bar' for main:Object
        from (irb):1
        from /home/durrantm/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :002 > bar if bar = false
(irb):2: warning: found = in conditional, should be ==
 => nil 

This is on my Ubuntu 14

like image 42
Michael Durrant Avatar answered Nov 15 '22 03:11

Michael Durrant


My answer is based on Ruby 2.1.2.

Adding with @Jörg W Mittag answer.

Another commonly confusing case is when using a modifier if:

p a if a = 0.zero? # => NameError: undefined local variable or method `a'

Rather than printing “true” you receive a NameError, “undefined local variable or method `a’”. Since ruby parses the bare a left of the if first and has not yet seen an assignment to a it assumes you wish to call a method. Ruby then sees the assignment to a and will assume you are referencing a local method.

The confusion comes from the out-of-order execution of the expression. First the local variable is assigned-to then you attempt to call a nonexistent method.

Based on above explanation -

bar if bar = false

simply returns nil, as the expression has been evaluated as false, the body of the code associated with the if modifier wouldn't be executed. nil is being returned by any block in Ruby, by default when there is no explicit default value.

like image 28
Arup Rakshit Avatar answered Nov 15 '22 05:11

Arup Rakshit