Project

General

Profile

Actions

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.

Status:
Closed
Target version:
-
ruby -v:
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]
[ruby-core:67945]

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 9 years ago

I really wish I could fix the typos at the title...

Updated by hanachin (Seiei Miyagi) about 9 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 9 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 9 years ago

  • Status changed from Open to Assigned
  • Assignee set to shugo (Shugo Maeda)

Updated by shugo (Shugo Maeda) about 9 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 9 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 9 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 9 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 9 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 9 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 9 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 9 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)
Actions #13

Updated by shugo (Shugo Maeda) about 9 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]
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0