-
Notifications
You must be signed in to change notification settings - Fork 203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Method definitions must not be nested catches singleton methods in minitest #207
Comments
lmao I didn't even know this was valid. Neat. |
I do this all the time. It's part of my stubbing toolkit in minitest. This + minitest/mock and you're set. |
I imagine this wouldn't be caught if I used the |
This is a neat trick for tests but is probably unwise to depend on it. A friend asked Matz directly about this behavior and he was told to not depend on it. But for a more concrete quote: https://bugs.ruby-lang.org/issues/11665
|
What I'm doing is not really a nested function as discussed in that thread, it's just defining a singleton method inside of another method. |
One resolution here would be to ignore any |
@nateberkopec that's a good point. |
This restriction on defining singleton methods with For reference, the alternatives to Starting with the Ractor issue: If you use Because of this, when the code in the built-in gems have been updated to be Ractor compatible, they've generally switched from using Instance methods of the type that you would define with def foo
...
self.class.class_exec do
def bar
...
end
end
end It's worth pointing out that For singleton methods, this is a different issue though. In that case, you are defining a method on a specific object that you have a reference to in your method. This is why defining a singleton method on an object is a really common way to define a localized extension of behavior, especially if you don't want it to be defined on all objects of that type. Not just in Minitest: Also in many other gems, including in Rails, and a lot of private code. The most common way that I've seen singleton methods defined is with For example, here are the alternatives for adding the same method every time: # Defining a non-customized singleton method using def style directly:
def foo(object)
...
def object.bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
object
end
# Defining a non-customized singleton method without nesting def, strictly because of the standard restriction:
def foo(object)
...
object.singleton_class.class_eval do
def bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
end
object
end
# But wait, BasicObject doesn't define singleton_class either.
# So let's try again to meet the standard restriction.
def foo(object)
...
# BasicObject has instance_eval or instance_exec, which are the same for blocks.
object.instance_exec do |obj|
# Still don't have access to singleton_class, so we have to use def obj. here and hope standard allows it.
def obj.bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
end
object
end
# If standard complains about the def obj. in the nested block, there's no other way to define a
# singleton method that works with BasicObject, so we have to use code in a string so standard
# won't recognize it as being code:
def foo(object)
...
object.instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
# This will definitely work with the standard restrictions, because it's a string.
def self.bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
RUBY
object
end For generating custom singleton methods, you can't directly use # Using define_singleton_method (doesn't work with BasicObject):
def foo(object)
...
object.define_singleton_method(:bar) do
... # This code can have Ractor sharing issues, even if object is made sharable or transferred.
end
object
end
# Using singleton_class (doesn't work with BasicObject):
def foo(object)
...
object.singleton_class.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
RUBY
object
end
# The only way to do custom methods that works with BasicObject is that instance_eval string code from before:
def foo(object)
...
object.instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
# This will definitely work with the standard restrictions, because it's a string.
def self.bar
... # This code is Ractor-safe if object is made sharable or transferred.
end
RUBY
object
end I bring up the BasicObject stuff because delegator classes are usually derived from BasicObject, and it pops up in other places. Nonetheless, you can see that for the case where the same singleton method is added to the object every time, the cleanest code is to use the For what it's worth, I really think that Matz's comment was specifically not about defining singleton methods, as he didn't mention that at all; he only mentioned instance methods. The fact that there is no other way than |
e.g.:
I could use
define_method
and a proc, but that just takes longer to type.The text was updated successfully, but these errors were encountered: