1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/spec/ruby/core/module/refine_spec.rb
Jeremy Evans 179384a668 Revert "Prevent SystemStackError when calling super in module with activated refinement"
This reverts commit eeef16e190.

This also reverts the spec change.

Preventing the SystemStackError would be nice, but there is valid
code that the fix breaks, and it is probably more common than cases
that cause the SystemStackError.

Fixes [Bug #17182]
2020-09-22 12:04:48 -07:00

1128 lines
24 KiB
Ruby

require_relative '../../spec_helper'
require_relative 'fixtures/refine'
describe "Module#refine" do
it "runs its block in an anonymous module" do
inner_self = nil
mod = Module.new do
refine String do
inner_self = self
end
end
mod.should_not == inner_self
inner_self.should be_kind_of(Module)
inner_self.name.should == nil
end
it "uses the same anonymous module for future refines of the same class" do
selves = []
mod = Module.new do
refine String do
selves << self
end
end
mod.module_eval do
refine String do
selves << self
end
end
selves[0].should == selves[1]
end
it "adds methods defined in its block to the anonymous module's public instance methods" do
inner_self = nil
mod = Module.new do
refine String do
def blah
"blah"
end
inner_self = self
end
end
inner_self.public_instance_methods.should include(:blah)
end
it "returns created anonymous module" do
inner_self = nil
result = nil
mod = Module.new do
result = refine String do
inner_self = self
end
end
result.should == inner_self
end
it "raises ArgumentError if not passed an argument" do
-> do
Module.new do
refine {}
end
end.should raise_error(ArgumentError)
end
it "raises TypeError if not passed a class" do
-> do
Module.new do
refine("foo") {}
end
end.should raise_error(TypeError)
end
it "accepts a module as argument" do
inner_self = nil
Module.new do
refine(Enumerable) do
def blah
end
inner_self = self
end
end
inner_self.public_instance_methods.should include(:blah)
end
it "applies refinements to the module" do
refinement = Module.new do
refine(Enumerable) do
def foo?
self.any? ? "yes" : "no"
end
end
end
foo = Class.new do
using refinement
def initialize(items)
@items = items
end
def result
@items.foo?
end
end
foo.new([]).result.should == "no"
foo.new([1]).result.should == "yes"
end
it "raises ArgumentError if not given a block" do
-> do
Module.new do
refine String
end
end.should raise_error(ArgumentError)
end
it "applies refinements to calls in the refine block" do
result = nil
Module.new do
refine(String) do
def foo; "foo"; end
result = "hello".foo
end
end
result.should == "foo"
end
it "doesn't apply refinements outside the refine block" do
Module.new do
refine(String) {def foo; "foo"; end}
-> {
"hello".foo
}.should raise_error(NoMethodError)
end
end
it "does not apply refinements to external scopes not using the module" do
Module.new do
refine(String) {def foo; 'foo'; end}
end
-> {"hello".foo}.should raise_error(NoMethodError)
end
# When defining multiple refinements in the same module,
# inside a refine block all refinements from the same
# module are active when a refined method is called
it "makes available all refinements from the same module" do
refinement = Module.new do
refine Integer do
def to_json_format
to_s
end
end
refine Array do
def to_json_format
"[" + map { |i| i.to_json_format }.join(", ") + "]"
end
end
refine Hash do
def to_json_format
"{" + map { |k, v| k.to_s.dump + ": " + v.to_json_format }.join(", ") + "}"
end
end
end
result = nil
Module.new do
using refinement
result = [{1 => 2}, {3 => 4}].to_json_format
end
result.should == '[{"1": 2}, {"3": 4}]'
end
it "does not make available methods from another refinement module" do
refinery_integer = Module.new do
refine Integer do
def to_json_format
to_s
end
end
end
refinery_array = Module.new do
refine Array do
def to_json_format
"[" + map { |i| i.to_json_format }.join(",") + "]"
end
end
end
result = nil
-> {
Module.new do
using refinery_integer
using refinery_array
[1, 2].to_json_format
end
}.should raise_error(NoMethodError)
end
# method lookup:
# * The prepended modules from the refinement for C
# * The refinement for C
# * The included modules from the refinement for C
# * The prepended modules of C
# * C
# * The included modules of C
describe "method lookup" do
it "looks in the object singleton class first" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
obj = refined_class.new
class << obj
def foo; "foo from singleton class"; end
end
result = obj.foo
end
result.should == "foo from singleton class"
end
it "looks in the included modules for builtin methods" do
result = ruby_exe(<<-RUBY)
a = Module.new do
def /(other) quo(other) end
end
refinement = Module.new do
refine Integer do
include a
end
end
result = nil
Module.new do
using refinement
result = 1 / 2
end
print result.class
RUBY
result.should == 'Rational'
end
it "looks in later included modules of the refined module first" do
a = Module.new do
def foo
"foo from A"
end
end
include_me_later = Module.new do
def foo
"foo from IncludeMeLater"
end
end
c = Class.new do
include a
end
refinement = Module.new do
refine c do; end
end
result = nil
Module.new do
using refinement
c.include include_me_later
result = c.new.foo
end
result.should == "foo from IncludeMeLater"
end
it "looks in prepended modules from the refinement first" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
include ModuleSpecs::IncludedModule
prepend ModuleSpecs::PrependedModule
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo from prepended module"
end
it "looks in refinement then" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine(refined_class) do
include ModuleSpecs::IncludedModule
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo from refinement"
end
it "looks in included modules from the refinement then" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
include ModuleSpecs::IncludedModule
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo from included module"
end
it "looks in the class then" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine(refined_class) { }
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo"
end
end
# methods in a subclass have priority over refinements in a superclass
it "does not override methods in subclasses" do
refined_class = ModuleSpecs.build_refined_class
subclass = Class.new(refined_class) do
def foo; "foo from subclass"; end
end
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = subclass.new.foo
end
result.should == "foo from subclass"
end
context "for methods accessed indirectly" do
it "is honored by Kernel#send" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.send :foo
end
result.should == "foo from refinement"
end
it "is honored by BasicObject#__send__" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.__send__ :foo
end
result.should == "foo from refinement"
end
it "is honored by Symbol#to_proc" do
refinement = Module.new do
refine Integer do
def to_s
"(#{super})"
end
end
end
result = nil
Module.new do
using refinement
result = [1, 2, 3].map(&:to_s)
end
result.should == ["(1)", "(2)", "(3)"]
end
ruby_version_is "" ... "2.6" do
it "is not honored by Kernel#public_send" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.public_send :foo
end
result.should == "foo"
end
end
ruby_version_is "2.6" do
it "is honored by Kernel#public_send" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo; "foo from refinement"; end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.public_send :foo
end
result.should == "foo from refinement"
end
end
it "is honored by string interpolation" do
refinement = Module.new do
refine Integer do
def to_s
"foo"
end
end
end
result = nil
Module.new do
using refinement
result = "#{1}"
end
result.should == "foo"
end
it "is honored by Kernel#binding" do
refinement = Module.new do
refine String do
def to_s
"hello from refinement"
end
end
end
klass = Class.new do
using refinement
def foo
"foo".to_s
end
def get_binding
binding
end
end
result = Kernel.eval("self.foo()", klass.new.get_binding)
result.should == "hello from refinement"
end
ruby_version_is "" ... "2.7" do
it "is not honored by Kernel#method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
-> {
Module.new do
using refinement
klass.new.method(:foo)
end
}.should raise_error(NameError, /undefined method `foo'/)
end
end
ruby_version_is "2.7" do
it "is honored by Kernel#method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
result = nil
Module.new do
using refinement
result = klass.new.method(:foo).class
end
result.should == Method
end
end
ruby_version_is "" ... "2.7" do
it "is not honored by Kernel#instance_method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
-> {
Module.new do
using refinement
klass.instance_method(:foo)
end
}.should raise_error(NameError, /undefined method `foo'/)
end
end
ruby_version_is "2.7" do
it "is honored by Kernel#method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
result = nil
Module.new do
using refinement
result = klass.instance_method(:foo).class
end
result.should == UnboundMethod
end
end
ruby_version_is "" ... "2.6" do
it "is not honored by Kernel#respond_to?" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
result = nil
Module.new do
using refinement
result = klass.new.respond_to?(:foo)
end
result.should == false
end
end
ruby_version_is "2.6" do
it "is honored by Kernel#respond_to?" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
result = nil
Module.new do
using refinement
result = klass.new.respond_to?(:foo)
end
result.should == true
end
end
ruby_version_is ""..."2.6" do
it "is not honored by &" do
refinement = Module.new do
refine String do
def to_proc(*args)
-> * { 'foo' }
end
end
end
-> do
Module.new do
using refinement
["hola"].map(&"upcase")
end
end.should raise_error(TypeError, /wrong argument type String \(expected Proc\)/)
end
end
ruby_version_is "2.6" do
it "is honored by &" do
refinement = Module.new do
refine String do
def to_proc(*args)
-> * { 'foo' }
end
end
end
result = nil
Module.new do
using refinement
result = ["hola"].map(&"upcase")
end
result.should == ['foo']
end
end
end
context "when super is called in a refinement" do
it "looks in the included to refinery module" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
include ModuleSpecs::IncludedModule
def foo
super
end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo from included module"
end
it "looks in the refined class" do
refined_class = ModuleSpecs.build_refined_class
refinement = Module.new do
refine refined_class do
def foo
super
end
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == "foo"
end
it "looks in the refined class from included module" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
a = Module.new do
def foo
[:A] + super
end
end
refinement = Module.new do
refine refined_class do
include a
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == [:A, :C]
end
it "looks in the refined ancestors from included module" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
subclass = Class.new(refined_class)
a = Module.new do
def foo
[:A] + super
end
end
refinement = Module.new do
refine refined_class do
include a
end
end
result = nil
Module.new do
using refinement
result = subclass.new.foo
end
result.should == [:A, :C]
end
# super in a method of a refinement invokes the method in the refined
# class even if there is another refinement which has been activated
# in the same context.
it "looks in the refined class first if called from refined method" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
refinement = Module.new do
refine refined_class do
def foo
[:R1]
end
end
end
refinement_with_super = Module.new do
refine refined_class do
def foo
[:R2] + super
end
end
end
result = nil
Module.new do
using refinement
using refinement_with_super
result = refined_class.new.foo
end
result.should == [:R2, :C]
end
it "looks only in the refined class even if there is another active refinement" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
refinement = Module.new do
refine refined_class do
def bar
"you cannot see me from super because I belong to another active R"
end
end
end
refinement_with_super = Module.new do
refine refined_class do
def bar
super
end
end
end
Module.new do
using refinement
using refinement_with_super
-> {
refined_class.new.bar
}.should raise_error(NoMethodError)
end
end
it "does't have access to active refinements for C from included module" do
refined_class = ModuleSpecs.build_refined_class
a = Module.new do
def foo
super + bar
end
end
refinement = Module.new do
refine refined_class do
include a
def bar
"bar is not seen from A methods"
end
end
end
Module.new do
using refinement
-> {
refined_class.new.foo
}.should raise_error(NameError) { |e| e.name.should == :bar }
end
end
it "does't have access to other active refinements from included module" do
refined_class = ModuleSpecs.build_refined_class
refinement_integer = Module.new do
refine Integer do
def bar
"bar is not seen from A methods"
end
end
end
a = Module.new do
def foo
super + 1.bar
end
end
refinement = Module.new do
refine refined_class do
include a
end
end
Module.new do
using refinement
using refinement_integer
-> {
refined_class.new.foo
}.should raise_error(NameError) { |e| e.name.should == :bar }
end
end
# https://bugs.ruby-lang.org/issues/16977
it "looks in the another active refinement if super called from included modules" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
a = Module.new do
def foo
[:A] + super
end
end
b = Module.new do
def foo
[:B] + super
end
end
refinement_a = Module.new do
refine refined_class do
include a
end
end
refinement_b = Module.new do
refine refined_class do
include b
end
end
result = nil
Module.new do
using refinement_a
using refinement_b
result = refined_class.new.foo
end
result.should == [:B, :A, :C]
end
it "looks in the current active refinement from included modules" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
a = Module.new do
def foo
[:A] + super
end
end
b = Module.new do
def foo
[:B] + super
end
end
refinement = Module.new do
refine refined_class do
def foo
[:LAST] + super
end
end
end
refinement_a_b = Module.new do
refine refined_class do
include a
include b
end
end
result = nil
Module.new do
using refinement
using refinement_a_b
result = refined_class.new.foo
end
result.should == [:B, :A, :LAST, :C]
end
it "looks in the lexical scope refinements before other active refinements" do
refined_class = ModuleSpecs.build_refined_class(for_super: true)
refinement_local = Module.new do
refine refined_class do
def foo
[:LOCAL] + super
end
end
end
a = Module.new do
using refinement_local
def foo
[:A] + super
end
end
refinement = Module.new do
refine refined_class do
include a
end
end
result = nil
Module.new do
using refinement
result = refined_class.new.foo
end
result.should == [:A, :LOCAL, :C]
end
end
it 'and alias aliases a method within a refinement module, but not outside it' do
Module.new do
using Module.new {
refine Array do
alias :orig_count :count
end
}
[1,2].orig_count.should == 2
end
-> { [1,2].orig_count }.should raise_error(NoMethodError)
end
it 'and alias_method aliases a method within a refinement module, but not outside it' do
Module.new do
using Module.new {
refine Array do
alias_method :orig_count, :count
end
}
[1,2].orig_count.should == 2
end
-> { [1,2].orig_count }.should raise_error(NoMethodError)
end
it "and instance_methods returns a list of methods including those of the refined module" do
methods = Array.instance_methods
methods_2 = []
Module.new do
refine Array do
methods_2 = instance_methods
end
end
methods.should == methods_2
end
# Refinements are inherited by module inclusion.
# That is, using activates all refinements in the ancestors of the specified module.
# Refinements in a descendant have priority over refinements in an ancestor.
context "module inclusion" do
it "activates all refinements from all ancestors" do
refinement_included = Module.new do
refine Integer do
def to_json_format
to_s
end
end
end
refinement = Module.new do
include refinement_included
refine Array do
def to_json_format
"[" + map { |i| i.to_s }.join(", ") + "]"
end
end
end
result = nil
Module.new do
using refinement
result = [5.to_json_format, [1, 2, 3].to_json_format]
end
result.should == ["5", "[1, 2, 3]"]
end
it "overrides methods of ancestors by methods in descendants" do
refinement_included = Module.new do
refine Integer do
def to_json_format
to_s
end
end
end
refinement = Module.new do
include refinement_included
refine Integer do
def to_json_format
"hello from refinement"
end
end
end
result = nil
Module.new do
using refinement
result = 5.to_json_format
end
result.should == "hello from refinement"
end
end
it 'does not list methods defined only in refinement' do
refine_object = Module.new do
refine Object do
def refinement_only_method
end
end
end
spec = self
klass = Class.new { instance_methods.should_not spec.send(:include, :refinement_only_method) }
instance = klass.new
instance.methods.should_not include :refinement_only_method
instance.respond_to?(:refinement_only_method).should == false
-> { instance.method :refinement_only_method }.should raise_error(NameError)
end
end