From 2de60da42d801426f8e059e63e6e1cf4f95717b8 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Wed, 14 Jun 2023 23:27:03 +0200 Subject: [PATCH] Be more picky about int/float arguments in Ruby There are implicit integer conversions using `to_int` and no conversions for floats. This is correct according to https://idiosyncratic-ruby.com/54-try-converting.html and Ruby's Discord https://discord.com/channels/518658712081268738/788748516352458752/1118618993276760105. --- .../tests/bindings/test_type_limits.py | 21 +++ .../tests/bindings/test_type_limits.rb | 149 ++++++++++++++++++ .../src/bindings/ruby/gen_ruby/mod.rs | 2 +- .../src/bindings/ruby/templates/Helpers.rb | 5 +- 4 files changed, 174 insertions(+), 3 deletions(-) diff --git a/fixtures/type-limits/tests/bindings/test_type_limits.py b/fixtures/type-limits/tests/bindings/test_type_limits.py index 8987aaa093..f767d3ddff 100644 --- a/fixtures/type-limits/tests/bindings/test_type_limits.py +++ b/fixtures/type-limits/tests/bindings/test_type_limits.py @@ -66,6 +66,15 @@ def test_larger_numbers(self): self.assertEqual(take_u64(10**19), 10**19) def test_non_integer(self): + self.assertRaises(TypeError, lambda: take_i8(None)) + self.assertRaises(TypeError, lambda: take_i16(None)) + self.assertRaises(TypeError, lambda: take_i32(None)) + self.assertRaises(TypeError, lambda: take_i64(None)) + self.assertRaises(TypeError, lambda: take_u8(None)) + self.assertRaises(TypeError, lambda: take_u16(None)) + self.assertRaises(TypeError, lambda: take_u32(None)) + self.assertRaises(TypeError, lambda: take_u64(None)) + self.assertRaises(TypeError, lambda: take_i8("0")) self.assertRaises(TypeError, lambda: take_i16("0")) self.assertRaises(TypeError, lambda: take_i32("0")) @@ -97,6 +106,15 @@ class A: self.assertRaises(TypeError, lambda: take_u64(A())) def test_integer_like(self): + self.assertEqual(take_i8(123), 123.0) + self.assertEqual(take_i16(123), 123.0) + self.assertEqual(take_i32(123), 123.0) + self.assertEqual(take_i64(123), 123.0) + self.assertEqual(take_u8(123), 123.0) + self.assertEqual(take_u16(123), 123.0) + self.assertEqual(take_u32(123), 123.0) + self.assertEqual(take_u64(123), 123.0) + self.assertEqual(take_i8(False), 0) self.assertEqual(take_i16(False), 0) self.assertEqual(take_i32(False), 0) @@ -129,6 +147,9 @@ def __index__(self): self.assertEqual(take_u64(A()), 123) def test_non_float(self): + self.assertRaises(TypeError, lambda: take_f32(None)) + self.assertRaises(TypeError, lambda: take_f64(None)) + self.assertRaises(TypeError, lambda: take_f32("0")) self.assertRaises(TypeError, lambda: take_f64("0")) diff --git a/fixtures/type-limits/tests/bindings/test_type_limits.rb b/fixtures/type-limits/tests/bindings/test_type_limits.rb index d32350e952..f76161a0d8 100644 --- a/fixtures/type-limits/tests/bindings/test_type_limits.rb +++ b/fixtures/type-limits/tests/bindings/test_type_limits.rb @@ -64,6 +64,155 @@ def test_larger_numbers assert_equal(UniffiTypeLimits.take_u16(10**4), 10**4) assert_equal(UniffiTypeLimits.take_u32(10**9), 10**9) assert_equal(UniffiTypeLimits.take_u64(10**19), 10**19) + + assert_raise FloatDomainError do UniffiTypeLimits.take_i8(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i16(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i32(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i64(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u8(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u16(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u32(-Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u64(-Float::INFINITY) end + + assert_raise FloatDomainError do UniffiTypeLimits.take_i8(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i16(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i32(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i64(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u8(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u16(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u32(Float::INFINITY) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u64(Float::INFINITY) end + + assert_raise FloatDomainError do UniffiTypeLimits.take_i8(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i16(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i32(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_i64(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u8(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u16(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u32(Float::NAN) end + assert_raise FloatDomainError do UniffiTypeLimits.take_u64(Float::NAN) end + end + class NonInteger + end + def test_non_integer + assert_raise TypeError do UniffiTypeLimits.take_i8(nil) end + assert_raise TypeError do UniffiTypeLimits.take_i16(nil) end + assert_raise TypeError do UniffiTypeLimits.take_i32(nil) end + assert_raise TypeError do UniffiTypeLimits.take_i64(nil) end + assert_raise TypeError do UniffiTypeLimits.take_u8(nil) end + assert_raise TypeError do UniffiTypeLimits.take_u16(nil) end + assert_raise TypeError do UniffiTypeLimits.take_u32(nil) end + assert_raise TypeError do UniffiTypeLimits.take_u64(nil) end + + assert_raise TypeError do UniffiTypeLimits.take_i8("0") end + assert_raise TypeError do UniffiTypeLimits.take_i16("0") end + assert_raise TypeError do UniffiTypeLimits.take_i32("0") end + assert_raise TypeError do UniffiTypeLimits.take_i64("0") end + assert_raise TypeError do UniffiTypeLimits.take_u8("0") end + assert_raise TypeError do UniffiTypeLimits.take_u16("0") end + assert_raise TypeError do UniffiTypeLimits.take_u32("0") end + assert_raise TypeError do UniffiTypeLimits.take_u64("0") end + + assert_raise TypeError do UniffiTypeLimits.take_i8(false) end + assert_raise TypeError do UniffiTypeLimits.take_i16(false) end + assert_raise TypeError do UniffiTypeLimits.take_i32(false) end + assert_raise TypeError do UniffiTypeLimits.take_i64(false) end + assert_raise TypeError do UniffiTypeLimits.take_u8(false) end + assert_raise TypeError do UniffiTypeLimits.take_u16(false) end + assert_raise TypeError do UniffiTypeLimits.take_u32(false) end + assert_raise TypeError do UniffiTypeLimits.take_u64(false) end + + assert_raise TypeError do UniffiTypeLimits.take_i8(true) end + assert_raise TypeError do UniffiTypeLimits.take_i16(true) end + assert_raise TypeError do UniffiTypeLimits.take_i32(true) end + assert_raise TypeError do UniffiTypeLimits.take_i64(true) end + assert_raise TypeError do UniffiTypeLimits.take_u8(true) end + assert_raise TypeError do UniffiTypeLimits.take_u16(true) end + assert_raise TypeError do UniffiTypeLimits.take_u32(true) end + assert_raise TypeError do UniffiTypeLimits.take_u64(true) end + + assert_raise TypeError do UniffiTypeLimits.take_i8(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_i16(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_i32(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_i64(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_u8(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_u16(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_u32(NonInteger.new) end + assert_raise TypeError do UniffiTypeLimits.take_u64(NonInteger.new) end + end + class IntegerLike + def to_int + 123 + end + end + def test_integer_like + assert_equal(UniffiTypeLimits.take_i8(123.0), 123) + assert_equal(UniffiTypeLimits.take_i16(123.0), 123) + assert_equal(UniffiTypeLimits.take_i32(123.0), 123) + assert_equal(UniffiTypeLimits.take_i64(123.0), 123) + assert_equal(UniffiTypeLimits.take_u8(123.0), 123) + assert_equal(UniffiTypeLimits.take_u16(123.0), 123) + assert_equal(UniffiTypeLimits.take_u32(123.0), 123) + assert_equal(UniffiTypeLimits.take_u64(123.0), 123) + + assert_equal(UniffiTypeLimits.take_i8(-0.5), 0) + assert_equal(UniffiTypeLimits.take_i16(-0.5), 0) + assert_equal(UniffiTypeLimits.take_i32(-0.5), 0) + assert_equal(UniffiTypeLimits.take_i64(-0.5), 0) + assert_equal(UniffiTypeLimits.take_u8(-0.5), 0) + assert_equal(UniffiTypeLimits.take_u16(-0.5), 0) + assert_equal(UniffiTypeLimits.take_u32(-0.5), 0) + assert_equal(UniffiTypeLimits.take_u64(-0.5), 0) + + assert_equal(UniffiTypeLimits.take_i8(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_i16(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_i32(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_i64(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_u8(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_u16(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_u32(IntegerLike.new), 123) + assert_equal(UniffiTypeLimits.take_u64(IntegerLike.new), 123) + end + class NonFloat + end + def test_non_float + assert_raise TypeError do UniffiTypeLimits.take_f32(nil) end + assert_raise TypeError do UniffiTypeLimits.take_f64(nil) end + + assert_raise TypeError do UniffiTypeLimits.take_f32("0") end + assert_raise TypeError do UniffiTypeLimits.take_f64("0") end + + assert_raise TypeError do UniffiTypeLimits.take_f32(false) end + assert_raise TypeError do UniffiTypeLimits.take_f64(false) end + + assert_raise TypeError do UniffiTypeLimits.take_f32(true) end + assert_raise TypeError do UniffiTypeLimits.take_f64(true) end + + assert_raise RangeError do UniffiTypeLimits.take_f32(1i) end + assert_raise RangeError do UniffiTypeLimits.take_f64(1i) end + + assert_raise TypeError do UniffiTypeLimits.take_f32(NonFloat.new) end + assert_raise TypeError do UniffiTypeLimits.take_f64(NonFloat.new) end + end + def test_float_like + assert_equal(UniffiTypeLimits.take_f32(456), 456.0) + assert_equal(UniffiTypeLimits.take_f64(456), 456.0) + end + def test_special_floats + assert_equal(UniffiTypeLimits.take_f32(Float::INFINITY), Float::INFINITY) + assert_equal(UniffiTypeLimits.take_f64(Float::INFINITY), Float::INFINITY) + + assert_equal(UniffiTypeLimits.take_f32(-Float::INFINITY), -Float::INFINITY) + assert_equal(UniffiTypeLimits.take_f64(-Float::INFINITY), -Float::INFINITY) + + assert_equal(UniffiTypeLimits.take_f32(0.0).to_s, "0.0") + assert_equal(UniffiTypeLimits.take_f64(0.0).to_s, "0.0") + + assert_equal(UniffiTypeLimits.take_f32(-0.0).to_s, "-0.0") + assert_equal(UniffiTypeLimits.take_f64(-0.0).to_s, "-0.0") + + assert(UniffiTypeLimits.take_f32(Float::NAN).nan?) + assert(UniffiTypeLimits.take_f64(Float::NAN).nan?) end def test_strings assert_raise Encoding::InvalidByteSequenceError do UniffiTypeLimits.take_string("\xff") end # invalid byte diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index ceddd998e6..2e3103ad93 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -224,7 +224,7 @@ mod filters { Type::UInt16 => format!("{ns}::uniffi_in_range({nm}, \"u16\", 0, 2**16)"), Type::UInt32 => format!("{ns}::uniffi_in_range({nm}, \"u32\", 0, 2**32)"), Type::UInt64 => format!("{ns}::uniffi_in_range({nm}, \"u64\", 0, 2**64)"), - Type::Float32 | Type::Float64 => format!("{nm}.to_f"), + Type::Float32 | Type::Float64 => nm.to_string(), Type::Boolean => format!("{nm} ? true : false"), Type::Object { .. } | Type::Enum(_) | Type::Record(_) => nm.to_string(), Type::String | Type::Bytes => format!("{ns}::uniffi_utf8({nm})"), diff --git a/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb b/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb index e79757e6f2..c36f9430b1 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb @@ -1,11 +1,12 @@ def self.uniffi_in_range(i, type_name, min, max) - i = i.to_i + raise TypeError, "no implicit conversion of #{i} into Integer" unless i.respond_to?(:to_int) + i = i.to_int raise RangeError, "#{type_name} requires #{min} <= value < #{max}" unless (min <= i && i < max) i end def self.uniffi_utf8(v) - v = v.to_s.encode(Encoding::UTF_8) + v = v.to_str.encode(Encoding::UTF_8) raise Encoding::InvalidByteSequenceError, "not a valid UTF-8 encoded string" unless v.valid_encoding? v end