Skip to content

Commit d8fd9b5

Browse files
committed
[Kernel] Tweak signatures for conversion methods, and add unit tests in
1 parent 36ba133 commit d8fd9b5

5 files changed

Lines changed: 195 additions & 47 deletions

File tree

core/kernel.rbs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,12 @@ module Kernel : BasicObject
481481
#
482482
def self?.Complex: (_ToC complex_like, ?exception: true) -> Complex
483483
| (_ToC complex_like, exception: bool) -> Complex?
484-
| (Numeric | String real, ?Numeric | String imag, ?exception: true) -> Complex
485-
| (Numeric | String real, ?Numeric | String imag, exception: bool) -> Complex?
486-
| (untyped, ?untyped, ?exception: bool) -> Complex?
484+
| (Numeric numeric, ?exception: bool) -> Complex
485+
| (String real_or_both, ?exception: true) -> Complex
486+
| (untyped real_or_both, exception: bool) -> Complex?
487+
| (Numeric | String real, Numeric | String imag, ?exception: true) -> Complex
488+
| (Numeric | String real, Integer | Float | Rational | Complex imag, exception: bool) -> Complex
489+
| (Numeric | String real, untyped imag, exception: bool) -> Complex?
487490

488491
# <!--
489492
# rdoc-file=kernel.rb
@@ -503,7 +506,7 @@ module Kernel : BasicObject
503506
#
504507
def self?.Float: (_ToF float_like, ?exception: true) -> Float
505508
| (_ToF float_like, exception: bool) -> Float?
506-
| (untyped, ?exception: bool) -> Float?
509+
| (untyped, exception: bool) -> Float?
507510

508511
# <!--
509512
# rdoc-file=object.c
@@ -525,7 +528,7 @@ module Kernel : BasicObject
525528
# Hash(nil) # => {}
526529
# Hash([]) # => {}
527530
#
528-
def self?.Hash: [K, V] (nil | [] _empty) -> Hash[K, V]
531+
def self?.Hash: [K, V] (nil | []) -> Hash[K, V]
529532
| [K, V] (hash[K, V] hash_like) -> Hash[K, V]
530533

531534
# <!--
@@ -615,7 +618,7 @@ module Kernel : BasicObject
615618
| (int | _ToI int_like, exception: bool) -> Integer?
616619
| (string str, int base, ?exception: true) -> Integer
617620
| (string str, int base, exception: bool) -> Integer?
618-
| (untyped, ?untyped, ?exception: bool) -> Integer?
621+
| (untyped, ?int base, exception: bool) -> Integer?
619622

620623
# <!--
621624
# rdoc-file=rational.c
@@ -656,14 +659,19 @@ module Kernel : BasicObject
656659
#
657660
def self?.Rational: (int | _ToR rational_like, ?exception: true) -> Rational
658661
| (int | _ToR rational_like, exception: bool) -> Rational?
659-
| (int | _ToR numer, ?int | _ToR denom, ?exception: true) -> Rational
660-
| (int | _ToR numer, ?int | _ToR denom, exception: bool) -> Rational?
661-
| [T] (Numeric & _RationalDiv[T] numer, Numeric denom, ?exception: bool) -> T
662+
| (int | _ToR numer, int | _ToR denom, ?exception: true) -> Rational
663+
| (int | _ToR numer, int | _ToR denom, exception: bool) -> Rational?
662664
| [T < Numeric] (T value, 1, ?exception: bool) -> T
663-
| (untyped, ?untyped, ?exception: bool) -> Rational?
665+
| [T] (Numeric & _RationalDiv[T] numer, Numeric denom, ?exception: bool) -> T
666+
| (untyped, ?untyped, exception: bool) -> Rational?
664667

668+
# An interface used in `Kernel.Rational` when both arguments are `Numeric`s,
669+
# but don't define `to_r` or `to_int`.
670+
#
671+
# The return type of the division is the return type of `Rational(numer, denom)`.
665672
interface _RationalDiv[T]
666-
def /: (Numeric) -> T
673+
# Divide the numerator by `denom`
674+
def /: (Numeric denom) -> T
667675
end
668676

669677
# <!--

lib/rbs/test/type_check.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,12 @@ def value(val, type)
356356
when Types::Variable
357357
true
358358
when Types::Literal
359-
type.literal == val
359+
begin
360+
type.literal == val
361+
rescue NoMethodError
362+
raise if defined?(val.==)
363+
false
364+
end
360365
when Types::Union
361366
type.types.any? {|type| value(val, type) }
362367
when Types::Intersection

lib/rbs/unit_test/type_assertions.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,11 @@ def send_setup(method_type, receiver, method, args, proc)
180180
)
181181
errors = typecheck.method_call(method, method_type, trace, errors: [])
182182

183-
assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}"
183+
assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, proc { "Call trace does not match with given method type: #{trace.inspect}" }
184184

185185
method_defs = method_defs(method)
186186
all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.each_annotation.to_a) }
187-
assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"
187+
assert all_errors.any? {|es| es.empty? }, proc { "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}" }
188188

189189
raise exception if exception
190190

sig/unit_test/type_assertions.rbs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ module RBS
3030
type target_type = Types::ClassInstance | Types::ClassSingleton
3131

3232
interface _BaseAssertions
33-
def assert: (untyped, ?String?) -> void
33+
def assert: (untyped, ?String? | Proc) -> void
3434

35-
def refute: (untyped, ?String?) -> void
35+
def refute: (untyped, ?String? | Proc) -> void
3636

37-
def assert_empty: (untyped, ?String?) -> void
37+
def assert_empty: (untyped, ?String | nil | Proc) -> void
3838

3939
def assert_operator: (untyped, Symbol, *untyped) -> void
4040

4141
def notify: (untyped) -> void
4242

43-
def assert_predicate: (untyped, Symbol, ?String?) -> void
43+
def assert_predicate: (untyped, Symbol, ?String? | Proc) -> void
4444

45-
def refute_predicate: (untyped, Symbol, ?String?) -> void
45+
def refute_predicate: (untyped, Symbol, ?String? | Proc) -> void
4646
end
4747

4848
module ClassMethods

test/stdlib/Kernel_test.rb

Lines changed: 163 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,73 +25,208 @@ def test_Array
2525
assert_send_type "(nil) -> []",
2626
Kernel, :Array, nil
2727

28-
with_array(1r, 2r).chain([ToA.new(1r,2r)]).each do |ary|
29-
assert_send_type "(::array[Rational] | ::_ToA[Rational]) -> Array[Rational]",
30-
Kernel, :Array, ary
28+
with_untyped do |ele|
29+
with_array(ele, ele).and ToA.new(ele, ele) do |ary|
30+
assert_send_type "[T] (array[T] | _ToA[T]) -> Array[T]",
31+
Kernel, :Array, ary
32+
end
33+
34+
next if defined?(ele.to_a) || defined?(ele.to_ary)
35+
assert_send_type "[T] (T) -> [T]",
36+
Kernel, :Array, ele
3137
end
38+
end
39+
40+
def test_Complex
41+
# (_ToC complex_like, ?exception: true) -> Complex
42+
assert_send_type "(_ToC) -> Complex",
43+
Kernel, :Complex, ToC.new
44+
assert_send_type "(_ToC, exception: true) -> Complex",
45+
Kernel, :Complex, ToC.new, exception: true
46+
47+
# (_ToC complex_like, exception: bool) -> Complex?
48+
assert_send_type "(_ToC, exception: bool) -> Complex",
49+
Kernel, :Complex, ToC.new, exception: false
50+
assert_send_type "(_ToC, exception: bool) -> nil",
51+
Kernel, :Complex, Class.new(BlankSlate){ def to_c = fail }.new, exception: false
3252

33-
assert_send_type "(Rational) -> [Rational]",
34-
Kernel, :Array, 1r
53+
numeric = Class.new(Numeric).new
54+
55+
# (Numeric numeric, ?exception: bool) -> Complex
56+
with 1, 1r, 1.0, (1+0i), numeric do |real|
57+
assert_send_type "(Numeric) -> Complex",
58+
Kernel, :Complex, real
59+
60+
# Single `Numeric`s can never fail
61+
with_bool do |exception|
62+
assert_send_type "(Numeric, exception: bool) -> Complex",
63+
Kernel, :Complex, real, exception: exception
64+
end
65+
end
66+
67+
# (String real_or_both, ?exception: true) -> Complex
68+
assert_send_type "(String) -> Complex",
69+
Kernel, :Complex, '1'
70+
assert_send_type "(String, exception: true) -> Complex",
71+
Kernel, :Complex, '1', exception: true
72+
73+
# (untyped real_or_both, exception: bool) -> Complex?
74+
with_untyped.and 'oops' do |real_untype|
75+
assert_send_type '(untyped, exception: bool) -> Complex?',
76+
Kernel, :Complex, real_untype, exception: false
77+
end
78+
79+
with '1', 1, 1r, 1.0, (1+0i), numeric do |real|
80+
with '2', 2, 2r, 2.0, (2+0i), numeric do |imag|
81+
# (Numeric | String real, Numeric | String imag, ?exception: true) -> Complex
82+
assert_send_type "(Numeric | String, Numeric | String) -> Complex",
83+
Kernel, :Complex, real, imag
84+
assert_send_type "(Numeric | String, Numeric | String, exception: true) -> Complex",
85+
Kernel, :Complex, real, imag, exception: true
86+
87+
# Complex has an awkward edgecase where `exception: false` will unconditionally return `nil`
88+
# if the imaginary argument is not one of the builtin `Numeric`s. Oddly enough, it's not for
89+
# the `real` one...
90+
case imag
91+
when Integer, Float, Rational, Complex
92+
# (Numeric | String real, Integer | Float | Rational | Complex imag, exception: bool) -> Complex
93+
assert_send_type "(Numeric | String, Integer | Float | Rational | Complex, exception: bool) -> Complex",
94+
Kernel, :Complex, real, imag, exception: false
95+
end
96+
end
97+
98+
# (Numeric | String real, untyped, exception: bool) -> Complex?
99+
with_untyped.and 'oops', numeric do |imag|
100+
next if [Integer, Float, Rational, Complex].any? { _1 === imag }
101+
assert_send_type "(Numeric | String, untyped, exception: bool) -> nil",
102+
Kernel, :Complex, real, imag, exception: false
103+
end
104+
end
35105
end
36106

107+
37108
def test_Float
38-
with_float 1.0 do |float|
39-
assert_send_type "(::float) -> Float",
40-
Kernel, :Float, float
41-
assert_send_type "(::float, exception: true) -> Float",
42-
Kernel, :Float, float, exception: true
43-
assert_send_type "(::float, exception: bool) -> Float?",
44-
Kernel, :Float, float, exception: false
109+
with 1, 1.0, ToF.new(1.0), '1e3' do |float_like|
110+
assert_send_type "(_ToF) -> Float",
111+
Kernel, :Float, float_like
112+
assert_send_type "(_ToF, exception: true) -> Float",
113+
Kernel, :Float, float_like, exception: true
114+
assert_send_type "(_ToF, exception: bool) -> Float",
115+
Kernel, :Float, float_like, exception: false
45116
end
46117

47-
assert_send_type "(untyped, ?exception: bool) -> Float?",
48-
Kernel, :Float, :hello, exception: false
118+
with_untyped do |untyped|
119+
next if defined? untyped.to_f
120+
assert_send_type "(untyped, exception: bool) -> nil",
121+
Kernel, :Float, untyped, exception: false
122+
end
49123
end
50124

51125
def test_Hash
52-
assert_send_type "(nil) -> Hash[untyped, untyped]",
126+
assert_send_type "[K, V] (nil) -> Hash[K, V]",
53127
Kernel, :Hash, nil
54-
assert_send_type "([]) -> Hash[untyped, untyped]",
128+
assert_send_type "[K, V] ([]) -> Hash[K, V]",
55129
Kernel, :Hash, []
56130

57131
with_hash 'a' => 3 do |hash|
58-
assert_send_type "(::hash[String, Integer]) -> Hash[String, Integer]",
132+
assert_send_type "[K, V] (hash[K, V]) -> Hash[K, V]",
59133
Kernel, :Hash, hash
60134
end
61135
end
62136

63137
def test_Integer
64-
with_int(1).chain([ToI.new(1)]).each do |int|
65-
assert_send_type "(::int | ::_ToI) -> Integer",
138+
with_int.and ToI.new do |int|
139+
assert_send_type "(int | _ToI) -> Integer",
66140
Kernel, :Integer, int
67-
assert_send_type "(::int | ::_ToI, exception: true) -> Integer",
141+
assert_send_type "(int | _ToI, exception: true) -> Integer",
68142
Kernel, :Integer, int, exception: true
69-
assert_send_type "(::int | ::_ToI, exception: bool) -> Integer?",
143+
assert_send_type "(int | _ToI, exception: bool) -> Integer?",
70144
Kernel, :Integer, int, exception: false
71145
end
72146

73147
with_string "123" do |string|
74148
with_int 8 do |base|
75-
assert_send_type "(::string, ::int) -> Integer",
149+
assert_send_type "(string, int) -> Integer",
76150
Kernel, :Integer, string, base
77-
assert_send_type "(::string, ::int, exception: true) -> Integer",
151+
assert_send_type "(string, int, exception: true) -> Integer",
78152
Kernel, :Integer, string, base, exception: true
79-
assert_send_type "(::string, ::int, exception: bool) -> Integer?",
153+
assert_send_type "(string, int, exception: bool) -> Integer?",
80154
Kernel, :Integer, string, base, exception: false
81155
end
82156
end
83157

84-
assert_send_type "(untyped, ?exception: bool) -> Integer?",
85-
Kernel, :Integer, :hello, exception: false
158+
with_untyped do |untyped|
159+
assert_send_type "(untyped, exception: bool) -> Integer?",
160+
Kernel, :Integer, untyped, exception: false
161+
162+
with_int 10 do |base|
163+
assert_send_type "(untyped, int, exception: bool) -> Integer?",
164+
Kernel, :Integer, untyped, base, exception: false
165+
end
166+
end
167+
end
168+
169+
def test_Rational
170+
with_int(1).and ToR.new(1r) do |numer|
171+
assert_send_type "(int | _ToR) -> Rational",
172+
Kernel, :Rational, numer
173+
assert_send_type "(int | _ToR, exception: true) -> Rational",
174+
Kernel, :Rational, numer, exception: true
175+
assert_send_type "(int | _ToR, exception: bool) -> Rational",
176+
Kernel, :Rational, numer, exception: false
177+
178+
with_int(2).and ToR.new(2r) do |denom|
179+
assert_send_type "(int | _ToR, int | _ToR) -> Rational",
180+
Kernel, :Rational, numer, denom
181+
assert_send_type "(int | _ToR, int | _ToR, exception: true) -> Rational",
182+
Kernel, :Rational, numer, denom, exception: true
183+
assert_send_type "(int | _ToR, int | _ToR, exception: bool) -> Rational",
184+
Kernel, :Rational, numer, denom, exception: false
185+
end
186+
end
187+
188+
bad_int = Class.new(BlankSlate){ def to_int = fail }.new
189+
bad_rat = Class.new(BlankSlate){ def to_r = fail }.new
190+
with bad_int, bad_rat do |bad_numer|
191+
assert_send_type "(int | _ToR, exception: bool) -> nil",
192+
Kernel, :Rational, bad_numer, exception: false
193+
assert_send_type "(int | _ToR, int | _ToR, exception: bool) -> nil",
194+
Kernel, :Rational, bad_numer, bad_numer, exception: false
195+
end
196+
197+
198+
numeric = Class.new(Numeric).new
199+
assert_send_type "[T < _Numeric] (T numer, 1) -> T",
200+
Kernel, :Rational, numeric, 1
201+
assert_send_type "[T < _Numeric] (T numer, 1, exception: bool) -> T",
202+
Kernel, :Rational, numeric, 1, exception: true
203+
assert_send_type "[T < _Numeric] (T numer, 1, exception: bool) -> T",
204+
Kernel, :Rational, numeric, 1, exception: false
205+
206+
numeric_div = Class.new(Numeric){ def /(other) = :hello }.new
207+
208+
assert_send_type "[T] (Numeric & Kernel::_RationalDiv[T] numer, Numeric denom) -> T",
209+
Kernel, :Rational, numeric_div, numeric
210+
assert_send_type "[T] (Numeric & Kernel::_RationalDiv[T] numer, Numeric denom, exception: bool) -> T",
211+
Kernel, :Rational, numeric_div, numeric, exception: true
212+
assert_send_type "[T] (Numeric & Kernel::_RationalDiv[T] numer, Numeric denom, exception: bool) -> T",
213+
Kernel, :Rational, numeric_div, numeric, exception: false
214+
215+
with_untyped do |numer|
216+
with_untyped do |denom|
217+
assert_send_type "(untyped, untyped, exception: bool) -> Rational?",
218+
Kernel, :Rational, numer, denom, exception: false
219+
end
220+
end
86221
end
87222

88223
def test_String
89224
with_string do |string|
90-
assert_send_type "(::string) -> String",
225+
assert_send_type "(string) -> String",
91226
Kernel, :String, string
92227
end
93228

94-
assert_send_type "(::_ToS) -> String",
229+
assert_send_type "(_ToS) -> String",
95230
Kernel, :String, ToS.new
96231
end
97232

0 commit comments

Comments
 (0)