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

3

6

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. ?

Betjeman answered 14/8, 2014 at 11:24 Comment(1)
No one seems to be answering the "did it change" questionBacteriolysis
B
1

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

Bacteriolysis answered 14/8, 2014 at 11:40 Comment(5)
Thanks for the details. And I suppose you do get an error with 2.1, right?Betjeman
Updated with that info.Bacteriolysis
Amazing that you can switch between all those versions. I only have the newest one, and your report helps.Betjeman
rvm has been the key tool for that for me for about 3 years. I can just do rvm use 2.1.2 to switch. Well worth checking out - rvm.io or just \curl -sSL https://get.rvm.io | bash -s stableBacteriolysis
Some folks have switched to rbenv to manage their ruby versions but rvm does what I need and I haven't seen a reason to switch.Bacteriolysis
I
5

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.

Ibby answered 14/8, 2014 at 11:34 Comment(1)
You say "unitialized" (typo btw) and that it will "evaluate to nil". Would it be wrong to say it's initialized as nil? Is it not actually nil yet?Monocarpic
N
1

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.

Navicert answered 14/8, 2014 at 11:36 Comment(0)
B
1

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

Bacteriolysis answered 14/8, 2014 at 11:40 Comment(5)
Thanks for the details. And I suppose you do get an error with 2.1, right?Betjeman
Updated with that info.Bacteriolysis
Amazing that you can switch between all those versions. I only have the newest one, and your report helps.Betjeman
rvm has been the key tool for that for me for about 3 years. I can just do rvm use 2.1.2 to switch. Well worth checking out - rvm.io or just \curl -sSL https://get.rvm.io | bash -s stableBacteriolysis
Some folks have switched to rbenv to manage their ruby versions but rvm does what I need and I haven't seen a reason to switch.Bacteriolysis

© 2022 - 2024 — McMap. All rights reserved.