Bug #10818
closedExtrange behaviour when apliying a refinement inside eval
Description
When you activate a refinement inside an a string using eval
with a binding, the refinement is sill active the next time you call eval
with the same binding.
Strangely enough, this will only happen if there is an assignment at the code evaluated the first time. If you delete the assignment everything works as expected.
module M
refine String do
def foobar; puts 'foobar'; end
end
end
some_binding = class A; binding; end
str1 = <<EOF
a = 'something' # Without this everything works as expected
using M
'str'.foobar # Works fine
EOF
str2 = <<EOF
'str'.foobar # This time should fail but it doesn't
EOF
eval str1, some_binding
eval str2, some_binding
Acording to the RefinmentsSpec: "when main.using is invoked in a string given as the first argument of Kernel#eval, Kernel#instance_eval, or Module#module_eval, the end of the scope is the end of the string."
Which contradicts with this code's behavior.
Updated by pabloh (Pablo Herrero) about 10 years ago
I really wish I could fix the typos at the title...
Updated by hanachin (Seiei Miyagi) about 10 years ago
The Kernel.#eval
behaves like:
eval('a = 42')
eval('p a')
# bar.rb:2:in `eval': undefined local variable or method `a' for main:Object (NameError)
# from bar.rb:2:in `eval'
# from bar.rb:2:in `<main>'
But Binding#eval
behaves like:
b = binding
b.eval("a = 42")
b.eval("p a")
# => 42
So, I expect following code works fine, but it raises NoMethodError
in ruby 2.1.5, 2.2.0
class Foo; end
module M
refine(Foo) do
def bar
42
end
end
end
b = binding
b.eval('using M')
puts b.eval('Foo.new.bar')
# expected:
# 42
#
# actual 2.0.0:
# refinement.rb:4: warning: Refinements are experimental, and the behavior may change in future versions of Ruby!
# 42
#
# actual 2.1.5:
# refinement.rb:11:in `<main>': undefined method `bar' for #<Foo:0x007fe3040fd568> (NoMethodError)
# from refinement.rb:13:in `eval'
# from refinement.rb:13:in `<main>'
#
# actual 2.2.0
# refinement.rb:11:in `<main>': undefined method `bar' for #<Foo:0x007f9fba04e488> (NoMethodError)
# from refinement.rb:13:in `eval'
# from refinement.rb:13:in `<main>'
Updated by hanachin (Seiei Miyagi) about 10 years ago
When call Kernel.#eval
with binding
returns same result.
class Foo; end
module M
refine(Foo) do
def bar
42
end
end
end
b = binding
eval('using M', b)
puts eval('Foo.new.bar', b)
# expected:
# 42
#
# actual 2.0.0:
# refinement2.rb:4: warning: Refinements are experimental, and the behavior may change in future versions of Ruby!
# 42
#
# actual 2.1.5:
# refinement2.rb:11:in `<main>': undefined method `bar' for #<Foo:0x007fc47c865568> (NoMethodError)
# from refinement2.rb:13:in `eval'
# from refinement2.rb:13:in `<main>'
#
# actual 2.2.0:
# refinement2.rb:11:in `<main>': undefined method `bar' for #<Foo:0x007fa1f0852138> (NoMethodError)
# from refinement2.rb:13:in `eval'
# from refinement2.rb:13:in `<main>'
Updated by shugo (Shugo Maeda) about 10 years ago
- Status changed from Open to Assigned
- Assignee set to shugo (Shugo Maeda)
Updated by shugo (Shugo Maeda) about 10 years ago
Seiei Higa wrote:
So, I expect following code works fine, but it raises
NoMethodError
in ruby 2.1.5, 2.2.0
Refinements should be activated in a lexical scope, so NoMethodError should be raised in that case.
The problem originally reported is considered to be a bug, but it's difficult to fix because
the environment of binding is updated by eval to share local variables (see vm_eval.c:1293),
and refinement activation information is stored in the same area.
Ko1, do you have any idea to fix this bug?
Updated by hanachin (Seiei Miyagi) about 10 years ago
Refinements should be activated in a lexical scope, so NoMethodError should be raised in that case.
How about this case?
class C; end
module M
refine(C) do
def foo
42
end
end
using M
$b = binding
end
puts $b.eval('C.new.foo')
# result in 2.2.0:
# 42
The docs of Binding says
Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained.
and docs of Binding#eval says
Evaluates the Ruby expression(s) in string, in the binding’s context.
It's sounds good to retain refinements in binding's context.
Updated by pabloh (Pablo Herrero) about 10 years ago
Does it make any difference that the refinement at Seiei's example was already active before the string evaluation?.
Updated by pabloh (Pablo Herrero) about 10 years ago
Seiei Higa wrote:
How about this case?
class C; end module M refine(C) do def foo 42 end end using M $b = binding end puts $b.eval('C.new.foo') # result in 2.2.0: # 42
OTOH that's also allowing you to leak the active refinements outside the lexical scope...
Updated by shugo (Shugo Maeda) about 10 years ago
Pablo Herrero wrote:
Does it make any difference that the refinement at Seiei's example was already active before the string evaluation?.
If eval('using M', b) in Seiei's example is changed to eval('x = 1; using M', b),
refinements are activated in the subsequent eval.
It's because b's environment is updated to capture the new local variable x.
Updated by shugo (Shugo Maeda) about 10 years ago
- Assignee changed from shugo (Shugo Maeda) to matz (Yukihiro Matsumoto)
Seiei Higa wrote:
Refinements should be activated in a lexical scope, so NoMethodError should be raised in that case.
How about this case?
It might be a bug too.
The docs of Binding says
Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained.
and docs of Binding#eval says
Evaluates the Ruby expression(s) in string, in the binding’s context.
It's sounds good to retain refinements in binding's context.
The documentation of Binding should not justify the behavior of refinements,
because it was written before refinements are introduced into Ruby.
The original version of refinements are designed to be a more dynamic feature,
but it was changed to be more static to avoid confusion caused by implicit
refinement activation.
I'd like to hear Matz's opinion.
Updated by pabloh (Pablo Herrero) about 10 years ago
Shugo Maeda wrote:
Pablo Herrero wrote:
Does it make any difference that the refinement at Seiei's example was already active before the string evaluation?.
If eval('using M', b) in Seiei's example is changed to eval('x = 1; using M', b),
refinements are activated in the subsequent eval.
It's because b's environment is updated to capture the new local variable x.
I followed you there, but I meant the example where he activated the refinement outside the string and then stored the binding at the global variable. Sorry if I wasn't clear enough.
Updated by shugo (Shugo Maeda) about 10 years ago
- Assignee changed from matz (Yukihiro Matsumoto) to shugo (Shugo Maeda)
Shugo Maeda wrote:
I'd like to hear Matz's opinion.
I talked with Matz, and he said that a binding should keep refinements activation
information and the refinements should be activated in subsequent eval calls with
the binding.
So I'll change the behavior of eval as follows:
module M
refine String do
def foobar; puts 'foobar'; end
end
end
some_binding = class A; binding; end
str1 = <<EOF
using M
'str'.foobar
EOF
str2 = <<EOF
'str'.foobar
EOF
eval str1, some_binding # foobar
eval str2, some_binding # foobar (No exception is raised)
Updated by shugo (Shugo Maeda) about 10 years ago
- Status changed from Assigned to Closed
- % Done changed from 0 to 100
Applied in changeset r49851.
- vm_eval.c (eval_string_with_cref): A binding should keep
refinements activation information and the refinements should be
activated in subsequent eval calls with the binding.
[ruby-core:67945] [Bug #10818]