Project

General

Profile

Feature #16432

Using `_1` inside `binding.irb` will cause unintended behavior

Added by osyo (manga osyo) 8 months ago. Updated 7 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:96332]

Description

Summary

Calling binding.irb in a block that uses _1 and using _1 in irb will cause unintended behavior.

Steps to reproduce

  1. Use _1 in block
  2. Use binding.irb in block
  3. Use block with _1 in binding.irb
# test.rb
proc {
  binding.irb
  _1
}.call 42
# binding.irb
irb> (1..10).map { _1 + _1 }

Expected behavior

irb> _1
 => 42
irb> (1..10).map { _1 + _1 }
 = > [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Actual behavior

  • Refers to the _1 of the block that calledbinding.irb
irb> _1
 => 42
irb> (1..10).map { _1 + _1 }
 = > [84, 84, 84, 84, 84, 84, 84, 84, 84, 84]

I think this is an unintended behavior for the user.

Updated by nobu (Nobuyoshi Nakada) 8 months ago

It is same as the following code without irb.

proc {
  p eval("(1..10).map{_1+_1}") #=> [84, 84, 84, 84, 84, 84, 84, 84, 84, 84]
  _1
}.call(42)

This is because the eval code is executed at the current context exactly.
Another choice is to raise a syntax error at the runtime.

Updated by osyo (manga osyo) 8 months ago

Thanks comments :)

This is because the eval code is executed at the current context exactly.
Another choice is to raise a syntax error at the runtime.

Yes, and if _1 is nested, a syntax error will occur.

proc {
  _1
  # error: numbered parameter is already used in
  (1..10).map { _1 + _1 }
}.call 42

If _1 is nested, I think it is better to runtime error rather than unintended behavior.

# Expected behavior in case of runtime error
# OK
irb> _1
=> 42
# OK
irb> _1 + _1
=> 84
# NG: Runtime error
irb> (1..10).map { _1 + _1 }

Updated by ko1 (Koichi Sasada) 7 months ago

  • in short:
1.times{
  p _1 #=> 0
  eval("[:a, :b].each{p _1}")
  #=> 0
  #=> 0
}

mame: it can cause confusing bug.
ko1: we have two solutions:

  1. separate numbered parameter scope between in/out eval.

    • Issue: we can't see _1 variables from debugging reason.
    • allow Binding#local_variable_get("_1") for this purpose? How to get uplevel binding?
  2. Same semantics without eval.

    • issue: we can't write numbered parameters in this kind of context.
# 2.7.0 behavior
1.times{p _1
  eval('p _1')            #=> OK (0)
  eval('[:a].each{p _1}') #=> OK, but confusing (0)
}

# 1.
1.times{p _1
  eval('p _1')            #=> NameError
  eval('[:a].each{p _1}') #=> OK (:a)
}

# 2.
1.times{p _1
  eval('p _1')            #=> OK (0)
  eval('[:a].each{p _1}') #=> SyntaxError
}
1.times{binding.irb}

# start IRB session
irb> p _1
#=>
# 2.7.0 NameError
# 1. NameError
# 2. NameError

1.times{binding.irb;_1}
# start IRB session
irb> p _1
#=>
# 2.7.0 OK (0)
# 1. NameError
# 2. OK (0)

irb> [:a].each{p _1}
#=>
# 2.7.0 OK, but confusing (0)
# 1. OK (:a)
# 2. SyntaxError

matz: vote for 1

Conclusion:

  • Solution 1. Nobu will fix parse.y
  • hopefully backport to 2.7.1 (March 2020)
#4

Updated by nobu (Nobuyoshi Nakada) 7 months ago

  • Status changed from Open to Closed

Applied in changeset git|c171ab23e376b6c7f1094a77f137d916b0a403e6.


Separate numbered parameter scope in eval

[Feature #16432]

Also available in: Atom PDF