Bug #10818
closed
Extrange behaviour when apliying a refinement inside eval
Added by pabloh (Pablo Herrero) about 9 years ago.
Updated about 9 years ago.
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.
I really wish I could fix the typos at the title...
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>'
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>'
- Status changed from Open to Assigned
- Assignee set to shugo (Shugo Maeda)
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?
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.
Does it make any difference that the refinement at Seiei's example was already active before the string evaluation?.
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...
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.
- 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.
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.
- 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)
- 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]
Also available in: Atom
PDF
Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0