Bug #20965
open`it` vs `binding.local_variables`
Description
it
is not available in the list of binding.local_varaibles
, unlike numbered parameters:
p(proc { binding.local_variables }.call) # []
p(proc { |x| binding.local_variables }.call) # [:x]
p(proc { _1; binding.local_variables }.call) # [:_1]
p(proc { vars = binding.local_variables; _1; vars }.call) # [:_1, :vars]
p(proc { it; binding.local_variables }.call) # []
I wonder if it is deliberate or accidental.
Updated by zverok (Victor Shepelev) 17 days ago
- ruby -v set to ruby 3.4.0dev (2024-12-15T13:36:38Z master 366fd9642f) +PRISM [x86_64-linux]
Updated by shan (Shannon Skipper) 17 days ago
It does seem like that last one should be [:it]
for consistency if it wasn't intentional. +1
An aside, but it's also interesting that using _2
causes _1
to also be defined under Binding#local_variables.
proc { _9; local_variables }.call
# => [:_1, :_2, :_3, :_4, :_5, :_6, :_7, :_8, :_9]
proc { it; local_variables }.call
# => []
Updated by nobu (Nobuyoshi Nakada) 17 days ago
Updated by k0kubun (Takashi Kokubun) 17 days ago
- Related to Feature #18980: `it` as a default block parameter added
Updated by nobu (Nobuyoshi Nakada) 17 days ago
- Status changed from Open to Closed
Applied in changeset git|46fec0f62a1803d44edb8b06e39ac0f358e56670.
[Bug #20965] Define it
like an ordinary argument (#12398)
Also fixes [Bug #20955]
Updated by tompng (tomoya ishida) 17 days ago
I have a concern of making it
parameter an lvar.
1| 42.tap do
2| p binding.local_variable_get('it')
3| it /1/i
4| p it
5| end
When parser reads p it
at line 4, it
turns out to be a local variable, I think from the beginning of the block(from line 2).
But on line 3, it /1/i
is already parsed as if lvar it
does not exist.
It is impossible to re-parse the block again, so the original implementation(it parameter is not an lvar) makes sense to me.
Updated by mame (Yusuke Endoh) 13 days ago
- Status changed from Closed to Open
Let's revert this change.
Below is our local discussion of this issue with @nobu (Nobuyoshi Nakada) and @ko1 (Koichi Sasada).
First, consider the following example.
"foo".tap do
it
"bar".tap do
p eval("it") # what should happen?
end
end
There are three possible options.
- Raises an exeption
- Returns
"bar"
- Returns
"foo"
1 is the only realistic choice. 2 would require keeping all arguments conservatively even when it
does not appear lexically, which we want to avoid for a performance reason (including the possibility of future optimizations, as @ko1 (Koichi Sasada) said). 3 is clearly counterintuitive.
Then, consider the following example.
"foo".tap do
it
"bar".tap do
p binding.local_variables #=> []? [:it]?
eval("it")
end
end
If local_variables
contains :it
, eval("it")
is expected to return some value. However, it is impossible for the reason above. Therefore, local_variables
should return []
.
The difference between _1
and it
is that _1
is prohibited to be referenced outside of a block, while it
is not.
Note that if it
is defined as an ordinary local variable by an assignment, local_variables
should contain :it
.
Updated by k0kubun (Takashi Kokubun) 13 days ago
- Status changed from Open to Closed
Applied in changeset git|667a0f9f928be843a0810f2c61b633be1f8cd46a.
Revert "[Bug #20965] Define it
like an ordinary argument" (#12418)
Revert "[Bug #20965] Define it
like an ordinary argument (#12398)"
Reverts ruby/ruby#12398 as per https://bugs.ruby-lang.org/issues/20970#note-6 and https://bugs.ruby-lang.org/issues/20965#note-7.
We need more time to design the intended behavior, and it's too late for Ruby 3.4.
Updated by k0kubun (Takashi Kokubun) 13 days ago
- Status changed from Closed to Open
Since fixing this raised issues like https://bugs.ruby-lang.org/issues/20970 (particularly relevant to this #20965) and it's too close to the release to design the behavior properly, we reverted https://github.com/ruby/ruby/pull/12398 for Ruby 3.4.
Updated by zverok (Victor Shepelev) 13 days ago
Interestingly, numbered block parameters do this:
"foo".tap do
_1
"bar".tap do
p binding.local_variables #=> [_1]
eval("_1") # undefined local variable or method '_1' for main:Object (NameError)
end
end
I am not saying this is good behavior, though. The things I am concerned about (after the release, of course):
- Whatever the behavior is, it is deliberate and documented;
- Whatever the behavior is, it is internally consistent (e.g., in #20955 the thing that surprised me was an inconsistency between proc and lambda);
- Whatever the behavior is, I’d expect it to be as consistent as possible between
it
and numbered anonymous parameters.
(About the latter one: it
s behavior, after the change is reverted, actually seems more internally consistent at least between local_variables
and eval
, so maybe numbered parameters should follow?)
Updated by mame (Yusuke Endoh) 13 days ago
I see. I think this is simply buggy.
"foo".tap do
_1
"bar".tap do
p binding.local_variable_get(:_1) #=> "foo"
end
end
IMO, meta-programming APIs for numbered parameters and it
should be separated from binding#local_variable*
because they are not a local variable. It would be good to consider them towards 3.5.