5

Why does the last statement (with "if (tmp2 = foo)" at the end of the statement) fail?

def foo;5;end

# this one works
if (tmp = foo)
  puts tmp.to_s
end

# why this one fails
puts tmp2.to_s if (tmp2 = foo) #=> undefined local variable or method ‘tmp2’ for main:Object
Felix
  • 4,510
  • 2
  • 31
  • 46
Neeraj Singh
  • 2,116
  • 4
  • 19
  • 24

3 Answers3

8

It fails because of the way the parser works.

From the parser's point of view the variable tmp2 exists from the point in the code at which it is first assigned until the point where it goes out of scope. For this it does not matter, when (or if) the assignment is actually executed, just when the parser sees the assignment (i.e. it depends on the assignments position in the code).

Edit: To expand on that bit:

The decision whether a name is a local variable or a method call is made by the parser. The parser makes that decision solely based on whether or not it has already seen an assignment to that variable. So when the parser sees tmp2 before seeing tmp2 = ..., it decides that here tmp2 refers to a method. When that part of the code is actually executed, it tries to call the method tmp2 which does not exist so you get the error.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • Actually, it doesn't decide that tmp2 is a method. It decides that it can't decide if tmp2 is a method or a local variable. – Sarah Mei Sep 04 '09 at 16:57
  • I'm not sure what you mean by that. `tmp2 if tmp2=1` will call method_missing with the argument `tmp2` when there is no method `tmp2` when the code executes. So it does treat tmp2 as a method call. – sepp2k Sep 04 '09 at 17:00
  • If it had decided for certain that you were trying to call a method, you'd get "undefined method `tmp2' for #." Yes, it calls method_missing, but because it can't tell whether you intended tmp2 to be a method call or a local variable, you get the general error message. – Sarah Mei Sep 04 '09 at 17:08
  • You get the general error message because the designers of ruby knew that that error could be caused when trying to access local variable. However there is no way that after the parsing step, ruby will look at tmp2 and say "Hm, I wasn't sure what this was before, but now I realize this is a variable". After the parser decides that it's not a local variable, it will definitely be treated as a method. So I think it's fair to say "it decides that it refers to a method". – sepp2k Sep 04 '09 at 17:13
  • Or to phrase my comment another way: Ruby might not no whether your intent was to access a method or a local variable, which is why the error message also mentions that there is no local variable. However it does decide that it will treat the name as a method call at that point. – sepp2k Sep 04 '09 at 17:19
  • No. The parser knows that tmp2 must have one of three possible states. Either it's a local variable, or it's a method, or it's neither. It first examines local variables and doesn't find it. It then knows it's either a method, or it's neither. It calls method_missing and it's not found there either. So at that point it knows that tmp2 is in the third state. At no time did the parser think tmp2 was a method call. – Sarah Mei Sep 04 '09 at 17:35
  • It's a semantic issue, but I think it's important. Up until it does method_missing, a method call is one possibility for tmp2. But it's never the *only* possibility. – Sarah Mei Sep 04 '09 at 17:43
  • I think it's important to distinguish between parse time and run time here. The parser does not call method_missing. That happens at runtime. The only thing that happens at parse time is the decision whether tmp2 is a variable. At run time ruby never tries to look whether there is a variable named tmp2 (which there is) because it already "knows" that tmp2 is a method call. Otherwise the code would work. – sepp2k Sep 04 '09 at 17:49
  • If at run time the parser "knows" that tmp2 is a method call, why don't you get "undefined method `tmp2' for #" ? – Sarah Mei Sep 04 '09 at 17:57
0
def foo; 5; end
puts (tmp2 = foo) && tmp2 || nil
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
exiquio
  • 473
  • 2
  • 5
  • 12
0

The assignment operator creates the variable. As such, when it sees 'puts.temp2.to_s' it doesn't yet know what tmp2 is. If you change the code to:

def foo;5;end
tmp2=1
puts tmp2.to_s if (tmp2 = foo)

It will work (and output '5')

David Oneill
  • 12,502
  • 16
  • 58
  • 70
  • I think the OP's confusion comes from the fact that at the point where tmp2.to_s executes the variable tmp2 does technically exist (because the assignment executes before tmp2.to_s does). – sepp2k Sep 04 '09 at 16:55
  • But it is the parser that has to know about the variable. And that comes to the 'puts' statement first (everything's read in sequential order). It doesn't execute anything until the parser is happy. – David Oneill Sep 04 '09 at 17:00