Skip to content

Commit 2eafda6

Browse files
committed
[GR-19220] Fix implicit type coercion
PullRequest: truffleruby/3742
2 parents 73afc4b + 45b5285 commit 2eafda6

20 files changed

+195
-211
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Bug fixes:
4646
* Fix `Monitor#exit` to raise `ThreadError` when monitor not owned by the current thread (#2922, @andrykonchin).
4747
* Fix `MatchData#[]` to support negative `length` argument (#2929, @andrykonchin).
4848
* Fix `IO` line reading calls when using a multi-byte delimiter (`IO#{each,gets,readline,readlines,etc.}) (#2961, @vinistock, @nirvdrum).
49+
* Fix the exception type raised when type coercion raises a `NoMethodError` (#2903, @paracycle, @nirvdrum).
4950

5051
Compatibility:
5152

spec/ruby/core/array/plus_spec.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,23 @@
1414
(ary + ary).should == [1, 2, 3, 1, 2, 3]
1515
end
1616

17-
it "tries to convert the passed argument to an Array using #to_ary" do
18-
obj = mock('["x", "y"]')
19-
obj.should_receive(:to_ary).and_return(["x", "y"])
20-
([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"]
17+
describe "converts the passed argument to an Array using #to_ary" do
18+
it "successfully concatenates the resulting array from the #to_ary call" do
19+
obj = mock('["x", "y"]')
20+
obj.should_receive(:to_ary).and_return(["x", "y"])
21+
([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"]
22+
end
23+
24+
it "raises a Typeerror if the given argument can't be converted to an array" do
25+
-> { [1, 2, 3] + nil }.should raise_error(TypeError)
26+
-> { [1, 2, 3] + "abc" }.should raise_error(TypeError)
27+
end
28+
29+
it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do
30+
obj = mock("hello")
31+
obj.should_receive(:to_ary).and_raise(NoMethodError)
32+
-> { [1, 2, 3] + obj }.should raise_error(NoMethodError)
33+
end
2134
end
2235

2336
it "properly handles recursive arrays" do

spec/ruby/core/kernel/method_spec.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
m.call.should == :defined
3030
end
3131

32-
it "can be called even if we only repond_to_missing? method, true" do
32+
it "can be called even if we only respond_to_missing? method, true" do
3333
m = KernelSpecs::RespondViaMissing.new.method(:handled_privately)
3434
m.should be_an_instance_of(Method)
3535
m.call(1, 2, 3).should == "Done handled_privately([1, 2, 3])"
@@ -58,4 +58,23 @@ def method_missing(m)
5858
m = cls.new.method(:bar)
5959
m.call.should == :bar
6060
end
61+
62+
describe "converts the given name to a String using #to_str" do
63+
it "calls #to_str to convert the given name to a String" do
64+
name = mock("method-name")
65+
name.should_receive(:to_str).and_return("hash")
66+
Object.method(name).should == Object.method(:hash)
67+
end
68+
69+
it "raises a TypeError if the given name can't be converted to a String" do
70+
-> { Object.method(nil) }.should raise_error(TypeError)
71+
-> { Object.method([]) }.should raise_error(TypeError)
72+
end
73+
74+
it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do
75+
name = mock("method-name")
76+
name.should_receive(:to_str).and_raise(NoMethodError)
77+
-> { Object.method(name) }.should raise_error(NoMethodError)
78+
end
79+
end
6180
end

spec/ruby/core/math/cos_spec.rb

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
Math.cos(2*Math::PI).should be_close(1.0, TOLERANCE)
1616
end
1717

18-
1918
it "raises a TypeError unless the argument is Numeric and has #to_f" do
2019
-> { Math.cos("test") }.should raise_error(TypeError)
2120
end
@@ -24,14 +23,23 @@
2423
Math.cos(nan_value).nan?.should be_true
2524
end
2625

27-
it "raises a TypeError if the argument is nil" do
28-
-> { Math.cos(nil) }.should raise_error(TypeError)
29-
end
30-
31-
it "coerces its argument with #to_f" do
32-
f = mock_numeric('8.2')
33-
f.should_receive(:to_f).and_return(8.2)
34-
Math.cos(f).should == Math.cos(8.2)
26+
describe "coerces its argument with #to_f" do
27+
it "coerces its argument with #to_f" do
28+
f = mock_numeric('8.2')
29+
f.should_receive(:to_f).and_return(8.2)
30+
Math.cos(f).should == Math.cos(8.2)
31+
end
32+
33+
it "raises a TypeError if the given argument can't be converted to a Float" do
34+
-> { Math.cos(nil) }.should raise_error(TypeError)
35+
-> { Math.cos(:abc) }.should raise_error(TypeError)
36+
end
37+
38+
it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a Float" do
39+
object = mock_numeric('mock-float')
40+
object.should_receive(:to_f).and_raise(NoMethodError)
41+
-> { Math.cos(object) }.should raise_error(NoMethodError)
42+
end
3543
end
3644
end
3745

spec/ruby/core/module/alias_method_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ def uno_refined_method
8181
-> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError)
8282
end
8383

84+
it "raises a NoMethodError if the given name raises a NoMethodError during type coercion using to_str" do
85+
obj = mock("mock-name")
86+
obj.should_receive(:to_str).and_raise(NoMethodError)
87+
-> { @class.make_alias obj, :public_one }.should raise_error(NoMethodError)
88+
end
89+
8490
it "is a public method" do
8591
Module.should have_public_instance_method(:alias_method, false)
8692
end

spec/ruby/core/module/const_defined_spec.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,23 @@
8080
ConstantSpecs::ClassA.const_defined?(:CS_CONSTX).should == false
8181
end
8282

83-
it "calls #to_str to convert the given name to a String" do
84-
name = mock("ClassA")
85-
name.should_receive(:to_str).and_return("ClassA")
86-
ConstantSpecs.const_defined?(name).should == true
83+
describe "converts the given name to a String using #to_str" do
84+
it "calls #to_str to convert the given name to a String" do
85+
name = mock("ClassA")
86+
name.should_receive(:to_str).and_return("ClassA")
87+
ConstantSpecs.const_defined?(name).should == true
88+
end
89+
90+
it "raises a TypeError if the given name can't be converted to a String" do
91+
-> { ConstantSpecs.const_defined?(nil) }.should raise_error(TypeError)
92+
-> { ConstantSpecs.const_defined?([]) }.should raise_error(TypeError)
93+
end
94+
95+
it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do
96+
name = mock("classA")
97+
name.should_receive(:to_str).and_raise(NoMethodError)
98+
-> { ConstantSpecs.const_defined?(name) }.should raise_error(NoMethodError)
99+
end
87100
end
88101

89102
it "special cases Object and checks it's included Modules" do

spec/ruby/core/queue/initialize_spec.rb

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,29 @@
2222
q.should.empty?
2323
end
2424

25-
it "uses #to_a on the provided Enumerable" do
26-
enumerable = MockObject.new('mock-enumerable')
27-
enumerable.should_receive(:to_a).and_return([1, 2, 3])
28-
q = Queue.new(enumerable)
29-
q.size.should == 3
30-
q.should_not.empty?
31-
q.pop.should == 1
32-
q.pop.should == 2
33-
q.pop.should == 3
34-
q.should.empty?
25+
describe "converts the given argument to an Array using #to_a" do
26+
it "uses #to_a on the provided Enumerable" do
27+
enumerable = MockObject.new('mock-enumerable')
28+
enumerable.should_receive(:to_a).and_return([1, 2, 3])
29+
q = Queue.new(enumerable)
30+
q.size.should == 3
31+
q.should_not.empty?
32+
q.pop.should == 1
33+
q.pop.should == 2
34+
q.pop.should == 3
35+
q.should.empty?
36+
end
37+
38+
it "raises a TypeError if the given argument can't be converted to an Array" do
39+
-> { Queue.new(42) }.should raise_error(TypeError)
40+
-> { Queue.new(:abc) }.should raise_error(TypeError)
41+
end
42+
43+
it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do
44+
enumerable = MockObject.new('mock-enumerable')
45+
enumerable.should_receive(:to_a).and_raise(NoMethodError)
46+
-> { Queue.new(enumerable) }.should raise_error(NoMethodError)
47+
end
3548
end
3649

3750
it "raises TypeError if the provided Enumerable does not respond to #to_a" do

spec/ruby/core/string/append_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
describe "String#<<" do
66
it_behaves_like :string_concat, :<<
77
it_behaves_like :string_concat_encoding, :<<
8+
it_behaves_like :string_concat_type_coercion, :<<
89

910
it "raises an ArgumentError when given the incorrect number of arguments" do
1011
-> { "hello".send(:<<) }.should raise_error(ArgumentError)

spec/ruby/core/string/concat_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
describe "String#concat" do
66
it_behaves_like :string_concat, :concat
77
it_behaves_like :string_concat_encoding, :concat
8+
it_behaves_like :string_concat_type_coercion, :concat
89

910
it "takes multiple arguments" do
1011
str = "hello "

spec/ruby/core/string/plus_spec.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
require_relative 'shared/concat'
44

55
describe "String#+" do
6+
it_behaves_like :string_concat_encoding, :+
7+
it_behaves_like :string_concat_type_coercion, :+
8+
69
it "returns a new string containing the given string concatenated to self" do
710
("" + "").should == ""
811
("" + "Hello").should == "Hello"
@@ -31,6 +34,4 @@
3134
("hello" + StringSpecs::MyString.new("foo")).should be_an_instance_of(String)
3235
("hello" + StringSpecs::MyString.new("")).should be_an_instance_of(String)
3336
end
34-
35-
it_behaves_like :string_concat_encoding, :+
3637
end

0 commit comments

Comments
 (0)