Project

General

Profile

Actions

Bug #16631

closed

Local variable assignment occured in condition clause of postfix-if doesn't allow immediate access to this variable

Added by prijutme4ty (Ilya Vorontsov) about 4 years ago. Updated about 4 years ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:<unknown>]

Description

I've found that usual-if and postfix-if work differently when there is an assignment in condition.
In both cases, a value is assigned to a variable, but in postfix-if left part doesn't have access to a variable if it is a local variable which was first defined in a condition.

When a variable if defined everything goes as intended:

lv_predefined = nil
puts("lv_predefined inside postfix-if: `#{lv_predefined}`") if lv_predefined = 'some value'
# lv_predefined inside postfix-if: `some value`

puts("lv_predefined after postfix-if: `#{lv_predefined}`")
# lv_predefined after postfix-if: `some value`

When a local variable is not defined, it is assigned but is unavailable in postfix-if body:

puts("undef_lv_postfix inside postfix-if: `#{undef_lv_postfix}`") rescue 'error'  if undef_lv_postfix = 'some value'
# => error

puts("undef_lv_postfix after postfix-if: `#{undef_lv_postfix}`")
# undef_lv_postfix after postfix-if: `some value`

That's different from usual if, which defines a variable and make it available for its body:

if lv_if = 'some value'
    puts("lv_if inside usual-if: `#{lv_if}`")
end
# lv_if inside usual-if: `some value`

To make things even more confusing, instance variable are immediately available in postfix-if unlike local variables.

puts("@iv inside postfix-if: `#{@iv}`") if @iv = 'some value'
# @iv inside postfix-if: `some value`

puts("@iv after postfix-if: `#{@iv}`")
# @iv after postfix-if: `some value`

I think this behavior doesn't meet the principle of least surprise.
And here's a case where assignment in postfix-if is indeed useful. I wish to match some string against a pattern and extract some information from it, so I have the following code:

query = 'HGNC:1234'
# query = 'MGI:5678'
puts("human gene: #{match[1]}")  if match = query.match(/HGNC:(\d+)/)
puts("mouse gene: #{match[1]}")  if match = query.match(/MGI:(\d+)/)

These two lines look identical but they are not (because match is an undefined local variable if this code was invoked from scratch). For human genes this code raises an exception, for mouse genes it works as intended. Very subtle problem.

Actions #1

Updated by prijutme4ty (Ilya Vorontsov) about 4 years ago

  • Description updated (diff)
Actions #2

Updated by matz (Yukihiro Matsumoto) about 4 years ago

  • Status changed from Open to Closed

It's intentional.

The local variables only available after the first assignment in the lexical order ( note that it's not execution order ). This is the basic principle of local variables in Ruby. So if you have an assignment in the condition expression of the if/unless/while/until modifier (besides it's not encouraged), the local variable is not available in the body part because it appears before the first assignment.

Other variables e.g. instance variables do not have this principle as you described in the OP.

Matz.

Actions

Also available in: Atom PDF

Like0
Like0Like0