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 `<main>'
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.

···

--
Posted via http://www.ruby-forum.com/.

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 Rex

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 Rex

Since "while" was brought up, it's worth mentioning that this also
affects the while modifier:

puts var1.whatever while var1 = getnext

···

--
Posted via http://www.ruby-forum.com/.

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 "v1:#{v1||='Not created' if ((v1='created')!='')}" #=> v1:created
2. p "v2:#{v2||='Not CREATED' if (v2='created') != ''}" #=> v2:created
3. p "if:#{if (v3 = 'created') != ''; v3; "XYZ"; end}" #=> if:XYZ
4. p "v3:#{v3}" #=> v3:created
5. p "v4:#{if (v4 = 'created') != ''; v4; end}" #=> v4:created
6. 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 method `v5' \
        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

···

--
Posted via http://www.ruby-forum.com/\.

Found an existing rejected bug about this:

···

_____________________________________
http://bugs.ruby-lang.org/issues/6132

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?

--
Posted via http://www.ruby-forum.com/.

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

···

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

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 Rex

Igor Pirnovar 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.

···

--
Posted via http://www.ruby-forum.com/\.

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 Wed, Oct 31, 2012 at 1:53 AM, Mean L. <lists@ruby-forum.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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)

···

--
Posted via http://www.ruby-forum.com/\.

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

···

On Wed, Oct 31, 2012 at 4:28 PM, Eliezer Croitoru <eliezer@ngtech.co.il>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?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Brian Candler 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 method `v5' 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

···

--
Posted via http://www.ruby-forum.com/\.

Quoting Robert Klemme (shortcutter@googlemail.com):

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

···

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

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

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

-- Matma Rex

Igor Pirnovar 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 method `v5' 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.

···

--
Posted via http://www.ruby-forum.com/\.

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

···

On Wed, Oct 31, 2012 at 5:00 PM, Carlo E. Prelz <fluido@fluido.as> wrote:

        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 Klemme (shortcutter@googlemail.com):

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

Brian Candler wrote in post #1082746:

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

I enjoyed reading your last reply, and have to admit that in your
earlier post, I completely missed the significance of the above code,
which is a rather convincing evidence of how parser works, and that
everything you are saying except a few hints about my ignorance, which
inspired me to object to your initial rather provoking reply to my post,
is true.

Thank you, for this second and much better than first reply.

Cheers, igor

···

--
Posted via http://www.ruby-forum.com/\.

Quoting Robert Klemme (shortcutter@googlemail.com):

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

···

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

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

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 <fluido@fluido.as> wrote:

        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 Klemme (shortcutter@googlemail.com):

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

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

--
  Matthew Kerwin, 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

Quoting Matthew Kerwin (matthew@kerwin.net.au):

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

May be useful sometimes...

Carlo

···

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

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