From 529b1ea5f7dae6beefab2fa7146b28f2d1f87f91 Mon Sep 17 00:00:00 2001 From: moste00 Date: Fri, 3 Mar 2023 19:31:12 +0200 Subject: [PATCH 1/3] Add Integer.try_convert for Ruby 3.1 support --- spec/ruby/core/integer/try_convert_spec.rb | 23 ++++++++++++++++++++- spec/tags/core/integer/try_convert_tags.txt | 6 ------ src/main/ruby/truffleruby/core/integer.rb | 13 ++++++++++++ src/main/ruby/truffleruby/core/type.rb | 6 ++++++ 4 files changed, 41 insertions(+), 7 deletions(-) delete mode 100644 spec/tags/core/integer/try_convert_tags.txt diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb index 45c66eec7944..5c3ff5cebc3e 100644 --- a/spec/ruby/core/integer/try_convert_spec.rb +++ b/spec/ruby/core/integer/try_convert_spec.rb @@ -28,7 +28,28 @@ it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do obj = mock("to_int") obj.should_receive(:to_int).and_return(Object.new) - -> { Integer.try_convert obj }.should raise_error(TypeError) + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Object)") + end + + it "responds with a different error message when it raises a TypeError, depending on the type of the non-Integer object :to_int returns" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("A String") + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives String)") + end + + it "responds with a different error message when it raises a TypeError, depending on the type of the argument object :to_int was called on " do + class DifferentMockObject + def to_int + Object.new + end + end + -> { + Integer.try_convert DifferentMockObject.new + }.should raise_error(TypeError, "can't convert DifferentMockObject to Integer (DifferentMockObject#to_int gives Object)") end it "does not rescue exceptions raised by #to_int" do diff --git a/spec/tags/core/integer/try_convert_tags.txt b/spec/tags/core/integer/try_convert_tags.txt deleted file mode 100644 index 335849e4e5ca..000000000000 --- a/spec/tags/core/integer/try_convert_tags.txt +++ /dev/null @@ -1,6 +0,0 @@ -fails:Integer.try_convert returns the argument if it's an Integer -fails:Integer.try_convert returns nil when the argument does not respond to #to_int -fails:Integer.try_convert sends #to_int to the argument and returns the result if it's nil -fails:Integer.try_convert sends #to_int to the argument and returns the result if it's an Integer -fails:Integer.try_convert sends #to_int to the argument and raises TypeError if it's not a kind of Integer -fails:Integer.try_convert does not rescue exceptions raised by #to_int diff --git a/src/main/ruby/truffleruby/core/integer.rb b/src/main/ruby/truffleruby/core/integer.rb index 5acfc97ffd01..5618fc9533a5 100644 --- a/src/main/ruby/truffleruby/core/integer.rb +++ b/src/main/ruby/truffleruby/core/integer.rb @@ -332,6 +332,19 @@ def zero? self == 0 end + def self.try_convert(obj) + unless obj.respond_to?(:to_int) + return nil + end + + result = obj.to_int + if Primitive.nil?(result) or Primitive.object_kind_of?(result, Integer) + return result + end + + Truffle::Type.conversion_mismatch_into(obj, Integer, :to_int, result) + end + def self.sqrt(n) n = Primitive.rb_to_int(n) raise Math::DomainError if n.negative? diff --git a/src/main/ruby/truffleruby/core/type.rb b/src/main/ruby/truffleruby/core/type.rb index 19805c47de55..3ba1fc83a547 100644 --- a/src/main/ruby/truffleruby/core/type.rb +++ b/src/main/ruby/truffleruby/core/type.rb @@ -187,6 +187,12 @@ def self.conversion_mismatch(obj, cls, meth, res) raise TypeError, "can't convert #{oc} to #{cls} (#{oc}##{meth} gives #{Primitive.object_class(res)})" end + # Same as conversion mismatch, with a tiny difference in error message ("into" instead of "to") + def self.conversion_mismatch_into(obj, cls, meth, res) + oc = Primitive.object_class(obj) + raise TypeError, "can't convert #{oc} into #{cls} (#{oc}##{meth} gives #{Primitive.object_class(res)})" + end + def self.fits_into_int?(val) Integer === val && Primitive.integer_fits_into_int(val) end From c08d7b362f60481bc10155d7ba7bc68d836620f1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 7 Mar 2023 16:03:07 +0100 Subject: [PATCH 2/3] Remove spec which leaks a new global constant (DifferentMockObject) * It's redundant with the spec above. --- spec/ruby/core/integer/try_convert_spec.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb index 5c3ff5cebc3e..4bc7d3851a0b 100644 --- a/spec/ruby/core/integer/try_convert_spec.rb +++ b/spec/ruby/core/integer/try_convert_spec.rb @@ -41,17 +41,6 @@ }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives String)") end - it "responds with a different error message when it raises a TypeError, depending on the type of the argument object :to_int was called on " do - class DifferentMockObject - def to_int - Object.new - end - end - -> { - Integer.try_convert DifferentMockObject.new - }.should raise_error(TypeError, "can't convert DifferentMockObject to Integer (DifferentMockObject#to_int gives Object)") - end - it "does not rescue exceptions raised by #to_int" do obj = mock("to_int") obj.should_receive(:to_int).and_raise(RuntimeError) From 6876cd3768a5a30a3a02d40d5287c4b78c0edfcb Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 7 Mar 2023 15:51:24 +0100 Subject: [PATCH 3/3] Use Truffle::Type.try_convert for Integer.try_convert * Fix Truffle::Type.try_convert message to match CRuby. * Spec the message for all .try_convert TypeErrors. --- CHANGELOG.md | 1 + spec/ruby/core/array/try_convert_spec.rb | 2 +- spec/ruby/core/hash/try_convert_spec.rb | 2 +- spec/ruby/core/io/try_convert_spec.rb | 2 +- spec/ruby/core/regexp/try_convert_spec.rb | 6 ++++++ spec/ruby/core/string/try_convert_spec.rb | 2 +- src/main/ruby/truffleruby/core/integer.rb | 11 +---------- src/main/ruby/truffleruby/core/type.rb | 8 +------- 8 files changed, 13 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf7c14cf4a6..39f420a93cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ Compatibility: * Modify `Struct#{inspect,to_s}` to match MRI when the struct is nested inside of an anonymous class or module (@st0012, @nirvdrum). * `Fiber.current` and `Fiber#transfer` are available without `require 'fiber'` like in CRuby 3.1 (#2733, @eregon). * Add `freeze` keyword argument to `Marshal.load` (#2733, @andrykonchin). +* Add `Integer.try_convert` (#2733, @moste00, @eregon). Performance: diff --git a/spec/ruby/core/array/try_convert_spec.rb b/spec/ruby/core/array/try_convert_spec.rb index 47b4722d803a..bea881500658 100644 --- a/spec/ruby/core/array/try_convert_spec.rb +++ b/spec/ruby/core/array/try_convert_spec.rb @@ -39,7 +39,7 @@ it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do obj = mock("to_ary") obj.should_receive(:to_ary).and_return(Object.new) - -> { Array.try_convert obj }.should raise_error(TypeError) + -> { Array.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_ary gives Object)") end it "does not rescue exceptions raised by #to_ary" do diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb index 44195c501089..d359ae49d8cd 100644 --- a/spec/ruby/core/hash/try_convert_spec.rb +++ b/spec/ruby/core/hash/try_convert_spec.rb @@ -39,7 +39,7 @@ it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do obj = mock("to_hash") obj.should_receive(:to_hash).and_return(Object.new) - -> { Hash.try_convert obj }.should raise_error(TypeError) + -> { Hash.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Hash (MockObject#to_hash gives Object)") end it "does not rescue exceptions raised by #to_hash" do diff --git a/spec/ruby/core/io/try_convert_spec.rb b/spec/ruby/core/io/try_convert_spec.rb index 5fbd10b6faf3..a9e99de7aaa9 100644 --- a/spec/ruby/core/io/try_convert_spec.rb +++ b/spec/ruby/core/io/try_convert_spec.rb @@ -38,7 +38,7 @@ it "raises a TypeError if the object does not return an IO from #to_io" do obj = mock("io") obj.should_receive(:to_io).and_return("io") - -> { IO.try_convert(obj) }.should raise_error(TypeError) + -> { IO.try_convert(obj) }.should raise_error(TypeError, "can't convert MockObject to IO (MockObject#to_io gives String)") end it "propagates an exception raised by #to_io" do diff --git a/spec/ruby/core/regexp/try_convert_spec.rb b/spec/ruby/core/regexp/try_convert_spec.rb index be567e2130da..e775dbe97160 100644 --- a/spec/ruby/core/regexp/try_convert_spec.rb +++ b/spec/ruby/core/regexp/try_convert_spec.rb @@ -18,4 +18,10 @@ rex.should_receive(:to_regexp).and_return(/(p(a)t[e]rn)/) Regexp.try_convert(rex).should == /(p(a)t[e]rn)/ end + + it "raises a TypeError if the object does not return an Regexp from #to_regexp" do + obj = mock("regexp") + obj.should_receive(:to_regexp).and_return("string") + -> { Regexp.try_convert(obj) }.should raise_error(TypeError, "can't convert MockObject to Regexp (MockObject#to_regexp gives String)") + end end diff --git a/spec/ruby/core/string/try_convert_spec.rb b/spec/ruby/core/string/try_convert_spec.rb index 84415c4a7577..72ce5dd8b23a 100644 --- a/spec/ruby/core/string/try_convert_spec.rb +++ b/spec/ruby/core/string/try_convert_spec.rb @@ -39,7 +39,7 @@ it "sends #to_str to the argument and raises TypeError if it's not a kind of String" do obj = mock("to_str") obj.should_receive(:to_str).and_return(Object.new) - -> { String.try_convert obj }.should raise_error(TypeError) + -> { String.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to String (MockObject#to_str gives Object)") end it "does not rescue exceptions raised by #to_str" do diff --git a/src/main/ruby/truffleruby/core/integer.rb b/src/main/ruby/truffleruby/core/integer.rb index 5618fc9533a5..ca167fdac4d3 100644 --- a/src/main/ruby/truffleruby/core/integer.rb +++ b/src/main/ruby/truffleruby/core/integer.rb @@ -333,16 +333,7 @@ def zero? end def self.try_convert(obj) - unless obj.respond_to?(:to_int) - return nil - end - - result = obj.to_int - if Primitive.nil?(result) or Primitive.object_kind_of?(result, Integer) - return result - end - - Truffle::Type.conversion_mismatch_into(obj, Integer, :to_int, result) + Truffle::Type.try_convert(obj, Integer, :to_int) end def self.sqrt(n) diff --git a/src/main/ruby/truffleruby/core/type.rb b/src/main/ruby/truffleruby/core/type.rb index 3ba1fc83a547..98ab8b5ae201 100644 --- a/src/main/ruby/truffleruby/core/type.rb +++ b/src/main/ruby/truffleruby/core/type.rb @@ -187,12 +187,6 @@ def self.conversion_mismatch(obj, cls, meth, res) raise TypeError, "can't convert #{oc} to #{cls} (#{oc}##{meth} gives #{Primitive.object_class(res)})" end - # Same as conversion mismatch, with a tiny difference in error message ("into" instead of "to") - def self.conversion_mismatch_into(obj, cls, meth, res) - oc = Primitive.object_class(obj) - raise TypeError, "can't convert #{oc} into #{cls} (#{oc}##{meth} gives #{Primitive.object_class(res)})" - end - def self.fits_into_int?(val) Integer === val && Primitive.integer_fits_into_int(val) end @@ -373,7 +367,7 @@ def self.execute_try_convert(obj, cls, meth) if Primitive.nil?(ret) || Primitive.object_kind_of?(ret, cls) ret else - raise TypeError, "Coercion error: obj.#{meth} did NOT return a #{cls} (was #{Primitive.object_class(ret)})" + conversion_mismatch(obj, cls, meth, ret) end end