Bug?: local variable created in if modifier not available in modified expression

irb(main):001:0> local1 if local1 = “created”
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method local1' for main:Object from (irb):1 from /home/me/.rbenv/versions/1.9.3-p194/bin/irb:12:in
irb(main):002:0>

(local1 if local1 = “created”) is supposed to be equivalent to
(if local1 = “created” then local1 end)

but the later works as expected and the former does not.

This is a known quirk of parsing; variables are considered “declared”
only after the first assigment to them is found by the parser. Here,
the assigment is “lexically” after the access operation.

This could be considered a bug, I think it might even be already filed
at http://bugs.ruby-lang.org/. But it certainly is a code smell –
first, it’s bad to assign to variables in an if condition (MRI even
warns you about it), second, it’s bad to include any even slightly
non-trivial expression in the short if form.

You can also verify that it parses differently: install the
ruby_parser gem and use “ruby_parse” command to parse the following
code:

local1 if local1 = “created”

if local1 = “created”
local1
end

You get this:

s(:block,
s(:if, s(:lasgn, :local1, s(:str, “created”)), s(:call, nil, :local1),
nil),
s(:if, s(:lasgn, :local1, s(:str, “created”)), s(:lvar, :local1), nil))

In the first case, the “local1” is interpreted as a function call; in
the second, as a local variable reference.

– Matma R.

Found an existing rejected bug about this:


Ruby parses left to right, so it see “lo” on the assignment side of the
if. Since no local variable “lo” has been encountered yet it must be a
method. Ruby then encounters the assignment to lo and creates the local
variable.


I can’t say this makes sense to me. Ruby otherwise is a dynamic
language, but with respect to local vars it’s suddenly static, and the
parser wants to identify all local vars in one left to right parse pass,
before the code is run.

so even though at runtime

(a if a = 1)

local var ‘a’ already exists before it’s read, the parser has statically
bound the first ‘a’ to a method lookup path.

So the question is, why in a dynamic language is local variable binding
static? Is it a perf optimization?

Mean L. wrote in post #1082116:

Ruby parses left to right, so it see “lo” on the assignment side of the
if. Since no local variable “lo” has been encountered yet it must be a
method. Ruby then encounters the assignment to lo and creates the local
variable.


I can’t say this makes sense to me. Ruby otherwise is a dynamic
language, but with respect to local vars it’s suddenly static, and the
parser wants to identify all local vars in one left to right parse pass,
before the code is run.

Two important reasons:

  1. It would be enormously inefficient if every time you invoked an
    expression like ‘a’ you had to decide at run-time whether it was a
    variable or a method call

  2. It is much easier to inspect a piece of code and be able to say with
    certainty at any point whether ‘a’ is a variable or method call. (This
    is also a good reason why class and def start new scopes; you never need
    to look outside of the current method to resolve the variable/method
    ambiguity)

On Wed, Oct 31, 2012 at 1:53 AM, Mean L. [email protected] wrote:

I can’t say this makes sense to me. Ruby otherwise is a dynamic
language, but with respect to local vars it’s suddenly static, and the
parser wants to identify all local vars in one left to right parse pass,
before the code is run.

so even though at runtime

(a if a = 1)

local var ‘a’ already exists before it’s read,

It could, but it doesn’t - because of the current semantics. :slight_smile:

the parser has statically
bound the first ‘a’ to a method lookup path.

So the question is, why in a dynamic language is local variable binding
static? Is it a perf optimization?

Just a guess: it’s probably simpler to implement. The decision when a
variable exists can be made solely based on textual order of the
source code and does not need to take into account how expressions are
evaluated which is a much more complicated thing to evaluate. I
guess, Matz figured it’s not worthwhile for the few corner cases. And
the community has learned to live with it pretty well, I’d say.

Kind regards

robert

On 10/31/2012 4:52 PM, Bartosz Dziewoński wrote:

– Matma R.
I am wondering who will use such if condition?
it’s not even a if condition at all.
why would you declare the variable in the if statement?

Regards,
Eliezer

Oh, and in case it wasn’t apparent: you can just add

local1 = nil

… before:

local1 if local1 = “created”

… to declare the variable, so that it can be used.

– Matma R.

Subject: Re: bug?: local variable created in if modifier not available
in modified expression
Date: Thu 01 Nov 12 12:48:58AM +0900

Quoting Robert K. ([email protected]):

With an “if” it’s never necessary. And I do not think it makes a
difference performance wise.

Having spent my good share of debugging time, I want to add that it
happens to me more often than I would like to forget one of the two =
in equality tests. I am infinitely grateful to any mechanism that
warns me of single equals in ifs; mistaken single ='s in ifs are hard
bugs to catch.

Carlo

Since “while” was brought up, it’s worth mentioning that this also
affects the while modifier:

puts var1.whatever while var1 = getnext

On Wed, Oct 31, 2012 at 4:28 PM, Eliezer C.
[email protected]wrote:

I am wondering who will use such if condition?
it’s not even a if condition at all.
why would you declare the variable in the if statement?

That’s a good question. Generally assignments in conditions of control
flow statements only make sense for loops because that sometimes allows
to
avoid redundant code:

option 1: redundant code

line = io.gets

while line
puts line
line = io.gets
end

option 2: no redundant code and shorter

while line = io.gets
puts line
end

even better

io.each_line do |line|
puts line
end

With an “if” it’s never necessary. And I do not think it makes a
difference performance wise.

Kind regards

robert

Subject: Re: bug?: local variable created in if modifier not available
in modified expression
Date: Thu 01 Nov 12 03:57:47AM +0900

Quoting Robert K. ([email protected]):

You mean like

$ ruby -ce ‘if a = 9 then puts 1 end’
-e:1: warning: found = in conditional, should be ==
Syntax OK

?

Yes, that’s why I really like Ruby. But GCC can also be helpful in this:

#!/usr/bin/env ruby
File::open(’/tmp/test.c’,‘w’) do |f|
f.write <<-EOF
#include <stdio.h>
int main(int argc,char *argv[])
{
int i=1;
if(i=3)
printf(“Bla bla\n”);

return 0;
}
EOF
end
system(‘gcc -o /tmp/test -Wall /tmp/test.c’)

gcc prints this warning:

/tmp/test.c: In function ‘main’:
/tmp/test.c:5:3: warning: suggest parentheses around assignment used as
truth value [-Wparentheses]

Carlo

On Wed, Oct 31, 2012 at 5:00 PM, Carlo E. Prelz [email protected]
wrote:

happens to me more often than I would like to forget one of the two =
in equality tests. I am infinitely grateful to any mechanism that
warns me of single equals in ifs; mistaken single ='s in ifs are hard
bugs to catch.

You mean like

$ ruby -ce ‘if a = 9 then puts 1 end’
-e:1: warning: found = in conditional, should be ==
Syntax OK

?

Kind regards

robert

Subject: Re: bug?: local variable created in if modifier not available
in modified expression
Date: Thu 01 Nov 12 08:41:45AM +0900

Quoting Matthew K. ([email protected]):

Dude, did you seriously just write and compile a C program from within
a ruby script?

May be useful sometimes…

Carlo

Dude, did you seriously just write and compile a C program from within
a ruby script?

On 1 November 2012 05:11, Carlo E. Prelz [email protected] wrote:

int i=1;

/tmp/test.c: In function main:
/tmp/test.c:5:3: warning: suggest parentheses around assignment used as truth
value [-Wparentheses]

Carlo

  •     Se la Strada e la sua Virtu' non fossero state messe da parte,
    
  • K * Carlo E. Prelz - [email protected] che bisogno ci sarebbe
    •           di parlare tanto di amore e di rettitudine? (Chuang-Tzu)
      


Matthew K., B.Sc (CompSci) (Hons)
http://matthew.kerwin.net.au/
ABN: 59-013-727-651

“You’ll never find a programming language that frees
you from the burden of clarifying your ideas.” - xkcd

Matthew K. wrote in post #1082298:

Dude, did you seriously just write and compile a C program from within
a ruby script?

Have a look at the RubyInline gem.

Igor P. wrote in post #1082716:

Ignoring the confusing and to interpreter ambiguous syntax above, which
BTW can easily be fixed, we can see the problem in the code as written
is not that variables inside the conditional statement behave as block
local variable, or that in an assignment to a nonexistent variable
inside the conditional expression causes that variable to lose its
dynamic property, somehow turning it into a static variable in front of
the conditional expression!

Please don’t take any notice of this. There are no “static variables” or
“dynamic variables” in Ruby. Neither does operator precedence have
anything to do with this.

It is simply this: Ruby decides what local variables exist in a method
during a left-to-right parse of the code. At the first point that an
assignment is seen, e.g. “foo = …”, then “foo” is created as a local
variable - i.e. space is reserved for it on the stack frame - and from
this point to the end of the block or method, a bareword “foo” is
treated as an access to that local variable.

However, you can still force a method call using foo() or self.foo

def foo
“Method foo”
end
=> nil

foo
=> “Method foo”

foo = 123 if false
=> nil

foo
=> nil

foo()
=> “Method foo”

self.foo
=> “Method foo”

So at “foo = 123” onwards, bareword foo is a local variable. This occurs
when the code is parsed, so it doesn’t matter that the expression is
never even executed.

This rule is strictly left to right. Another example:

def bar
“Method bar”
end
=> nil

bar if bar = 123
(irb):4: warning: found = in conditional, should be ==
=> “Method bar”

The bar before the if is a method call bar(), because bar is only
available as a local variable from the ‘bar = 123’ assignment onwards.

So if you repeat this line, it changes its behaviour:

bar if bar = 123
(irb):5: warning: found = in conditional, should be ==
=> 123

That’s all there is to it.

Brian C. wrote in post #1082725:

def bar
“Method bar”
end
=> nil

bar if bar = 123
(irb):4: warning: found = in conditional, should be ==
=> “Method bar”

You are misleading us here with {{ def bar; “Method bar”; end }} before
the assignment in {{ bar if bar = 123 }}, because parser now thinks that
it sees the method bar. Try it with a new variable, that is not shadowed
by another token like in your case the bar method, and you will see,
that interpreter exits with the error! For instance:

p “v5:#{v5 if ((v5=‘created’)!=’’)}” #=> FAILS with:
# t.rb:28:in <main>': undefined local variable \ # or methodv5’ for main:Object (NameError)

… There are no “static variables” or “dynamic variables” in Ruby.
Neither does operator precedence have anything to do with this.

I completely agree with the part stating there are no static or dynamic
variables. I used the terminology above to disprove what the author of
this thread suggested, namely that with respect to local vars Ruby acts
as a statically typed language. I showed that even if for the sake of
the argument we accept his suggestion, it can be proved that variables
spring into existence as soon as they are assigned a value, and are
visible inside their scope exactly as expected.

Precedence may not be important, though I am not sure it is not, since
the run-time must wait for the condition to be evaluated, therefore it
will execute the assignment inside the ‘if’ expression in accordance
with precedence, namely prior ‘if’ expression itself is evaluated, hence
allowing the evaluation of the expression in front of the conditional,
the run-time was waiting for.

Also, the fact that {{ puts var||=‘n/a’ if var=123 }} works as expected,
and {{ puts var if var=123 }} does not, proves that evaluation here is
right-to-left regardless of the fact that parser works in the opposite
direction, after all, if parser would react to the first assignment it
encountered in ‘strictly’ left-to-right fashion the result should be
‘n/a’ and not 123 that was evaluated last.

Regards, igor

Mean L. wrote in post #1082083:

(local1 if local1 = “created”) is supposed to be equivalent to
(if local1 = “created” then local1 end)

but the later works as expected and the former does not.

Ignoring the confusing and to interpreter ambiguous syntax above, which
BTW can easily be fixed, we can see the problem in the code as written
is not that variables inside the conditional statement behave as block
local variable, or that in an assignment to a nonexistent variable
inside the conditional expression causes that variable to lose its
dynamic property, somehow turning it into a static variable in front of
the conditional expression!

Neither of these assumptions is correct. The reason, you did not get the
results as you expected is because you are not taking account, that the
conditional in this situation is executed last, since it has the lowest
precedence of all operators, the only operator with lower precedence is
the block expression (begin/end). The same is true for all conditional
keywords {{ if, until, while, unless }}, and you can expect they behave
in the same way as ‘if’.

In the following code you can see that neither is it true that variables
before the conditional behave as static variables nor, are the variables
inside the conditional expression, local to some invisible block:

  1. p “v2:#{v2||=‘Not CREATED’ if (v2=‘created’) != ‘’}” #=> v2:created
  2. p “if:#{if (v3 = ‘created’) != ‘’; v3; “XYZ”; end}” #=> if:XYZ
  3. p “v3:#{v3}” #=> v3:created
  4. p “v4:#{if (v4 = ‘created’) != ‘’; v4; end}” #=> v4:created
  5. p “v5:#{v5 if ((v5=‘created’)!=’’)}” #=> FAILS

Lines number 3 and 5 above show that a conditional expression ‘if’
returns the value of the last expression in its block expression.
However, when a conditional expression is used as modifier, obviously,
there is no return value there, and line #6 fails with:

<main>': undefined local variable or methodv5’
for main:Object (NameError)

You can salvage this situation by delaying the access to ‘v5’ variable,
by using v5||=‘Not created’ idiom, which proves, that variable ‘v5’
comes from inside the ‘if’ expression, and is indeed, the dynamically
created ‘v5’ rather than local static variable that shadows the
temporary inner conditional-block’s ‘v5’.

Regards, igor

Everybody, please ignore Igor, he’s a troll and he doesn’t know what
he’s talking about.

– Matma R.

Igor P. wrote in post #1082740:

You are misleading us here with {{ def bar; “Method bar”; end }} before
the assignment in {{ bar if bar = 123 }}, because parser now thinks that
it sees the method bar.

On the contrary, it is you who are misleading people with your
inaccurate information.

In your defence, I will say that ruby is a language which is not
formally specified or documented, and therefore it is easy to come up
with misinterpretations of what you see. However, there are people on
this list who will try to enlighten you, if you will take the trouble to
listen.

Try it with a new variable, that is not shadowed
by another token like in your case the bar method, and you will see,
that interpreter exits with the error! For instance:

p “v5:#{v5 if ((v5=‘created’)!=’’)}” #=> FAILS with:
# t.rb:28:in <main>': undefined local variable \ # or methodv5’ for main:Object (NameError)

Indeed it does, and the error message says “undefined local variable or
method”

Ruby has interpreted the first v5 as a method call, because no local
variable assignment statement exists lexically earlier in the scope. So
it has tried to call method v5(), and failed.

However it cannot know whether the programmer intended v5 to be a
variable or method. It could be that you intended to use a variable, but
mistyped the name:

v5 = nil
puts vv5 # Oops, I mistyped the variable name

vv6 = nil # Oops, I mistyped the variable name at assignment time
puts v6

Or it could be that you intended to call a method but mistyped it:

def v7; “ok”; end
puts vv7 # Oops, I mistyped the method name

So the best that Ruby can say is “undefined local variable or method”.
But what it actually means is: v5 is definitely not a local variable
at this point, so I tried invoking it as a method, but there is no
method called v5 at this point in time.

Note: whether v5 exists as a method or not can only be known at runtime,
because methods are created dynamically as the program runs.

Precedence may not be important, though I am not sure it is not, since
the run-time must wait for the condition to be evaluated, therefore it
will execute the assignment inside the ‘if’ expression in accordance
with precedence, namely prior ‘if’ expression itself is evaluated, hence
allowing the evaluation of the expression in front of the conditional,
the run-time was waiting for.

Wrong. The resolution of the variable name / method call ambiguity is
not done at run-time. It is done at parse time, when the source file
is being read in and the AST is being built, before the AST is executed.

If you don’t believe me, then try making a source file which cannot be
parsed, like this:

puts “hello”
class Foo # missing end

You will see something like this:

syntax error, unexpected $end

But notice that the word “hello” does not appear on your screen. This is
because execution cannot start until the file has been completely parsed
from top to bottom. Balancing class/end and def/end is a parse-time
activity.

Now, it is during this parsing phase that the decision is made as to
whether a bareword ‘foo’ is to be interpreted as a method call or as a
local variable reference. And this happens before even one statement
from this file has been executed.

Also, the fact that {{ puts var||=‘n/a’ if var=123 }} works as expected,
and {{ puts var if var=123 }} does not, proves that evaluation here is
right-to-left regardless of the fact that parser works in the opposite
direction, after all, if parser would react to the first assignment it
encountered in ‘strictly’ left-to-right fashion the result should be
‘n/a’ and not 123 that was evaluated last.

var||=‘n/a’ is an assignment to var and therefore springs a local
variable into existence at that point lexically. You can consider it as
a shortcut for var = var || ‘n/a’, although it is not exactly like that.

The execution sequence obviously requires the ‘if’ condition to be
evaluated before the LHS can be conditionally executed.

if var = 123 # assigns 123 to var, evaluates as true, hence LHS is
run

puts var||=‘n/a’ # prints 123, because var has already been assigned
to

So evaluation is right-before-left, at the time where the AST is
executed. This still does not change the fact that it is parsed
left-to-right.

Hence, if you have not assigned to var previously, then
puts var if var=123
is parsed as
puts var() if var=123

It treats the first bareword ‘var’ to be resolved as a method call; it
will fail unless a method called ‘var’ has been defined.

The final point you need to be clear on: whether an assignment is
executed or not makes no difference. It’s simply whether it is parsed
as one. So for example:

if false
baz = 123
end
puts baz # prints nil - does not give an error

Regards,

Brian.