From 5bc6fe87b3e6d31a151a2b9cd16188fe18c529fe Mon Sep 17 00:00:00 2001 From: SamW Date: Tue, 7 Nov 2023 12:43:36 -0800 Subject: [PATCH 1/6] updating kernel --- core/kernel.rbs | 361 ++++++++--- test/stdlib/Kernel_test.rb | 1217 +++++++++++++++--------------------- test/stdlib/test_helper.rb | 73 ++- test/stdlib/util/valid.rb | 2 + 4 files changed, 812 insertions(+), 841 deletions(-) create mode 100644 test/stdlib/util/valid.rb diff --git a/core/kernel.rbs b/core/kernel.rbs index 138118d93..046e3f960 100644 --- a/core/kernel.rbs +++ b/core/kernel.rbs @@ -191,9 +191,9 @@ module Kernel : BasicObject # c(4) #=> [] # c(5) #=> nil # - def self?.caller: (Integer start_or_range, ?Integer length) -> ::Array[String]? - | (::Range[Integer] start_or_range) -> ::Array[String]? - | () -> ::Array[String] + def self?.caller: () -> Array[String] + | (int start, ?int? length) -> Array[String]? + | (range[int?] range) -> Array[String]? # - # Returns the class of *obj*. This method must always be called with an explicit - # receiver, as #class is also a reserved word in Ruby. - # - # 1.class #=> Integer - # self.class #=> Object - # - def class: () -> untyped - # # Equivalent to Proc.new. # - def self?.proc: () { () -> untyped } -> Proc + def self?.proc: () { (*untyped, **untyped) -> untyped } -> Proc # + # Returns the class of *obj*. This method must always be called with an explicit + # receiver, as #class is also a reserved word in Ruby. + # + # 1.class #=> Integer + # self.class #=> Object + # + def class: () -> Class # # Creates a new Enumerator which will enumerate by calling `method` on `obj`, @@ -2306,7 +2456,9 @@ module Kernel : BasicObject # def enum_for: (Symbol method, *untyped, **untyped) ?{ (*untyped, **untyped) -> Integer } -> Enumerator[untyped, untyped] | () ?{ () -> Integer } -> Enumerator[untyped, self] - + # if no method is given, defaults to `each` + # size is enum's yield + # ^ todo %a{annotate:rdoc:skip} alias to_enum enum_for @@ -2345,7 +2497,7 @@ module Kernel : BasicObject # 1 == 1.0 #=> true # 1.eql? 1.0 #=> false # - def eql?: (untyped) -> bool + def eql?: (untyped other) -> bool # - # DO NOT USE THIS DIRECTLY. - # - # Hook method to return whether the *obj* can respond to *id* method or not. - # - # When the method name parameter is given as a string, the string is converted - # to a symbol. - # - # See #respond_to?, and the example of BasicObject. - # - %a{annotate:rdoc:copy:Object#respond_to_missing?} - private def respond_to_missing?: (Symbol, bool) -> bool + def respond_to?: (interned name, ?boolish include_all) -> boolish + # ^ repsonds with boolish via # c=Class.new{def respond_to_missing?(*)=:lol}.new; p c.respond_to? "a{3}" # + # DO NOT USE THIS DIRECTLY. + # + # Hook method to return whether the *obj* can respond to *id* method or not. + # + # When the method name parameter is given as a string, the string is converted + # to a symbol. + # + # See #respond_to?, and the example of BasicObject. + # + %a{annotate:rdoc:copy:Object#respond_to_missing?} + def respond_to_missing?: (interned name, boolish include_all) -> boolish + + def initialize_copy: (instance object) -> self - private def initialize_clone: (self object, ?freeze: bool?) -> self + def initialize_clone: (instance object, ?freeze: bool?) -> self - private def initialize_dup: (self object) -> self + def initialize_dup: (instance object) -> self end - -Kernel::RUBYGEMS_ACTIVATION_MONITOR: untyped diff --git a/test/stdlib/Kernel_test.rb b/test/stdlib/Kernel_test.rb index fb2551a5f..36e94e21d 100644 --- a/test/stdlib/Kernel_test.rb +++ b/test/stdlib/Kernel_test.rb @@ -1,986 +1,749 @@ -require_relative "test_helper" - -require "securerandom" +require_relative 'test_helper' class KernelSingletonTest < Test::Unit::TestCase include TypeAssertions - testing "singleton(::Kernel)" - - def test_Array - assert_send_type "(nil) -> []", - Kernel, :Array, nil - - with_array(1r, 2r).chain([ToA.new(1r,2r)]).each do |ary| - assert_send_type "(::array[Rational] | ::_ToA[Rational]) -> Array[Rational]", - Kernel, :Array, ary - end - - assert_send_type "(Rational) -> [Rational]", - Kernel, :Array, 1r - end - - def test_Float - with_float 1.0 do |float| - assert_send_type "(::float) -> Float", - Kernel, :Float, float - assert_send_type "(::float, exception: true) -> Float", - Kernel, :Float, float, exception: true - assert_send_type "(::float, exception: bool) -> Float?", - Kernel, :Float, float, exception: false - end - - assert_send_type "(untyped, ?exception: bool) -> Float?", - Kernel, :Float, :hello, exception: false - end + testing 'singleton(::Kernel)' - def test_Hash - assert_send_type "(nil) -> Hash[untyped, untyped]", - Kernel, :Hash, nil - assert_send_type "([]) -> Hash[untyped, untyped]", - Kernel, :Hash, [] - - with_hash 'a' => 3 do |hash| - assert_send_type "(::hash[String, Integer]) -> Hash[String, Integer]", - Kernel, :Hash, hash - end - end + def test_caller + assert_send_type '() -> Array[String]', + Kernel, :caller - def test_Integer - with_int(1).chain([ToI.new(1)]).each do |int| - assert_send_type "(::int | ::_ToI) -> Integer", - Kernel, :Integer, int - assert_send_type "(::int | ::_ToI, exception: true) -> Integer", - Kernel, :Integer, int, exception: true - assert_send_type "(::int | ::_ToI, exception: bool) -> Integer?", - Kernel, :Integer, int, exception: false - end + with_int 0 do |start| + assert_send_type '(int) -> Array[String]', + Kernel, :caller, start - with_string "123" do |string| - with_int 8 do |base| - assert_send_type "(::string, ::int) -> Integer", - Kernel, :Integer, string, base - assert_send_type "(::string, ::int, exception: true) -> Integer", - Kernel, :Integer, string, base, exception: true - assert_send_type "(::string, ::int, exception: bool) -> Integer?", - Kernel, :Integer, string, base, exception: false + with_int(2).and_nil do |length| + assert_send_type '(int, int?) -> Array[String]', + Kernel, :caller, start, length end end - assert_send_type "(untyped, ?exception: bool) -> Integer?", - Kernel, :Integer, :hello, exception: false - end - - - def test_String - with_string do |string| - assert_send_type "(::string) -> String", - Kernel, :String, string + with_int caller.length*2 do |start| + assert_send_type '(int) -> nil', + Kernel, :caller, start end - - assert_send_type "(::_ToS) -> String", - Kernel, :String, ToS.new end - def test_autoload? - with_interned :TestModuleForAutoload do |interned| - assert_send_type "(::interned) -> String?", - Kernel, :autoload?, interned - end + def test_caller_locations + assert_send_type '() -> Array[Thread::Backtrace::Location]', + Kernel, :caller_locations - autoload :TestModuleForAutoload, '/shouldnt/be/executed' + with_int 0 do |start| + assert_send_type '(int) -> Array[Thread::Backtrace::Location]', + Kernel, :caller_locations, start - with_interned :TestModuleForAutoload do |interned| - assert_send_type "(::interned) -> String?", - Kernel, :autoload?, interned + with_int(2).and_nil do |length| + assert_send_type '(int, int?) -> Array[Thread::Backtrace::Location]', + Kernel, :caller_locations, start, length + end end - end -end -class KernelTest < StdlibTest - target Kernel - discard_output - - def test_caller - caller(1, 2) - caller(1) - caller(1..2) - caller + with_int caller.length*2 do |start| + assert_send_type '(int) -> nil', + Kernel, :caller_locations, start + end end - def test_caller_locations - caller_locations(1, 2) - caller_locations(1) - caller_locations(1..2) - caller_locations - end + def test_catch + assert_send_type '(:foo) { (:foo) -> untyped } -> untyped', + Kernel, :catch, :foo do end - def test_catch_throw - catch do |tag| - throw tag - end - - catch("tag") do |tag| - throw tag - end + assert_send_type '() { (Object) -> untyped} -> untyped', + Kernel, :catch do end end - def test_class - Object.new.class - end def test_eval - eval "p" - eval "p", binding, "fname", 1 + with_string '1' do |src| + assert_send_type '(string) -> untyped', + Kernel, :eval, src + + [binding, nil].each do |scope| + assert_send_type '(string, Binding?) -> untyped', + Kernel, :eval, src, scope + + with_string __FILE__ do |filename| + assert_send_type '(string, Binding?, string) -> untyped', + Kernel, :eval, src, scope, filename + + with_int __LINE__ do |lineno| + assert_send_type '(string, Binding?, string, int) -> untyped', + Kernel, :eval, src, scope, filename, lineno + end + end + end + end end def test_block_given? - block_given? + assert_send_type '() -> bool', + Kernel, :block_given? end def test_local_variables - _ = x = 1 - local_variables + local_var = 3 + assert_send_type '() -> Array[Symbol]', + Kernel, :local_variables end def test_srand - srand - srand(10) - srand(10.5) - end - - def test_not_tilde - return if RUBY_VERSION >= "3.2.0" - - Object.new !~ Object.new - end - - def test_spaceship - Object.new <=> Object.new - end - - def test_eqeqeq - Object.new === Object.new - end - - def test_clone - Object.new.clone - Object.new.clone(freeze: false) - end - - def test_display - 1.display - 1.display($stderr) - - stdout = STDOUT.dup - STDOUT.reopen(IO::NULL) - Object.new.display() - Object.new.display(STDOUT) - Object.new.display(StringIO.new) - ensure - STDOUT.reopen(stdout) - end + with_int 0 do |int| + assert_send_type '(int) -> Integer', + Kernel, :srand, int + end - def test_dup - 1.dup + # Don't move this test, as it's also implicitly testing the `-> Integer` (ie last value) is + # actually correct, and won't return an `int`. + assert_send_type '() -> Integer', + Kernel, :srand end - def each(*args) - + def test_fork + assert_send_type '() { () -> void } -> Integer', + Kernel, :fork do 3 end + omit 'todo' + begin + child = assert_send_type '() -> Integer', + Kernel, :fork + rescue Test::Unit::AssertionFailedError + child = assert_send_type '() -> Integer', + Kernel, :fork + exit! 123 + else + exit! 12 if child.nil? + end end - def test_enum_for - enum_for :then - - enum_for :each, 1 - enum_for(:each, 1) { 2 } - - obj = Object.new + def test_Array + assert_send_type '(nil) -> []', + Kernel, :Array, nil - obj.enum_for(:instance_exec) - obj.enum_for(:instance_exec, 1,2,3) - obj.enum_for(:instance_exec, 1,2,3) { |x,y,z| x + y + z } + with_array(1r, 2r).and_chain(ToA.new(3r)) do |array_like| + assert_send_type '[T] (array[T] | _ToA[T]) -> []', + Kernel, :Array, array_like + end - obj.to_enum(:instance_exec) - obj.to_enum(:instance_exec, 1, 2, 3) - obj.to_enum(:instance_exec, 1, 2, 3) { |x, y, z| x + y + z } + assert_send_type '(Rational) -> [Rational]', + Kernel, :Array, 1r end - def test_eql? - Object.new.eql? 1 - end + def test_Complex + assert_send_type '(_ToC) -> Complex', + Kernel, :Complex, ToC.new + assert_send_type '(_ToC, exception: true) -> Complex', + Kernel, :Complex, ToC.new, exception: true + assert_send_type '(_ToC, exception: bool) -> nil', + Kernel, :Complex, Class.new{def to_c = raise}.new, exception: false + + # TODO: There's additional constraints on the `Numeric` here, but we need to figure out how + # we want `Numeric` to work before we can add `, Numeric.new` to the end of the list. + numerics = [1, 1r, 1.0, 1i] + + (numerics + ['1i']).each do |real| + assert_send_type '(Numeric | String) -> Complex', + Kernel, :Complex, real + assert_send_type '(Numeric | String, exception: true) -> Complex', + Kernel, :Complex, real, exception: true + + (numerics + ['1i']).each do |imag| + assert_send_type '(Numeric | String, Numeric | String) -> Complex', + Kernel, :Complex, real, imag + assert_send_type '(Numeric | String, Numeric | String, exception: true) -> Complex', + Kernel, :Complex, real, imag, exception: true + end + end - def test_extend - Object.new.extend Module.new - Object.new.extend Module.new, Module.new + assert_send_type '(String, exception: false) -> nil', + Kernel, :Complex, 'a', exception: false + assert_send_type '(String, String, exception: false) -> nil', + Kernel, :Complex, 'a', 'a', exception: false end - def test_fork - if Process.respond_to?(:fork) - exit! unless fork - fork { exit! } + def test_Float + [1, 1r, 1.0, '123e45', ToF.new].each do |float_like| + assert_send_type '(_ToF) -> Float', + Kernel, :Float, float_like + assert_send_type '(_ToF, exception: true) -> Float', + Kernel, :Float, float_like, exception: true end - end - def test_freeze - Object.new.freeze + assert_send_type '(_ToF, exception: false) -> nil', + Kernel, :Float, Class.new{def to_f = raise}.new, exception: false end - def test_frozen? - Object.new.frozen? - end - - def test_hash - Object.new.hash - end + def test_Hash + assert_send_type '(nil) -> {}', + Kernel, :Hash, nil + assert_send_type '([]) -> {}', + Kernel, :Hash, [] - def test_initialize_copy - Object.new.instance_eval do - initialize_copy(Object.new) + with_hash 'a' => 3, 'b' => 4 do |hash| + assert_send_type '(hash[String, Integer]) -> Hash[String, Integer]', + Kernel, :Hash, hash end end - def test_inspect - Object.new.inspect - end - - def test_instance_of? - Object.new.instance_of? String - end + def test_Integer + omit 'todo' + with_int.and_chain(ToI.new) do |int_like| + assert_send_type '(int | _ToI) -> Integer', + Kernel, :Integer, int_like + assert_send_type '(int | _ToI, exception: true) -> Integer', + Kernel, :Integer, int_like, exception: true + end + + assert_send_type '(_ToInt, exception: false) -> nil', + Kernel, :Integer, Class.new{def to_int = raise}.new, exception: false + assert_send_type '(_ToI, exception: false) -> nil', + Kernel, :Integer, Class.new{def to_i = raise}.new, exception: false + + with_int 16 do |base| + with_string 'ff' do |str| + assert_send_type '(string, int) -> Integer', + Kernel, :Integer, str, base + assert_send_type '(string, int, exception: true) -> Integer', + Kernel, :Integer, str, base, exception: true + end - def test_instance_variable_defined? - Object.new.instance_variable_defined?('@foo') - Object.new.instance_variable_defined?(:@bar) - end + with_string 'invalid!' do |str| + assert_send_type '(string, int, exception: false) -> nil', + Kernel, :Integer, str, base, exception: false + end - def test_instance_variable_get - Object.new.instance_variable_get('@foo') - Object.new.instance_variable_get(:@bar) + assert_send_type '(string, int, exception: false) -> nil', + Kernel, :Integer, Class.new{def to_str = raise}.new, base, exception: false + end + + assert_send_type '(string, int, exception: false) -> nil', + Kernel, :Integer, '12', Class.new{def to_int = raise}.new, exception: false end - def test_instance_variable_set - Object.new.instance_variable_set('@foo', 1) - Object.new.instance_variable_set(:@bar, 2) + def test_Rational + omit 'todo' end - def test_instance_variables - obj = Object.new - obj.instance_eval do - @foo = 1 + def test_String + with_string.and_chain(ToS.new) do |string_like| + assert_send_type '(string | _ToS) -> String', + Kernel, :String, string_like end - obj.instance_variables - end - - def test_is_a? - Object.new.is_a? String - Object.new.kind_of? Enumerable end - def test_method - Object.new.method(:tap) - Object.new.method('yield_self') - end + CALLEE_OUTSIDE_OF_A_METHOD = __callee__ + def test___callee__ + assert_send_type '() -> Symbol', + Kernel, :__callee__ - def test_methods - Object.new.methods - Object.new.methods true - Object.new.methods false + assert_type 'nil', CALLEE_OUTSIDE_OF_A_METHOD end - def test_nil? - Object.new.nil? + def test___dir__ + omit 'todo' end - def test_private_methods - Object.new.private_methods - Object.new.private_methods true - Object.new.private_methods false - end + METHOD_OUTSIDE_OF_A_METHOD = __method__ + def test___method__ + assert_type 'nil', METHOD_OUTSIDE_OF_A_METHOD - def test_protected_methods - Object.new.protected_methods - Object.new.protected_methods true - Object.new.protected_methods false + assert_send_type '() -> Symbol', + Kernel, :__method__ end - def test_public_method - Object.new.public_method(:tap) - Object.new.public_method('yield_self') - end + def test_backtick + # `cd` is on linux, macos, and windows, so it seems like a safe choice. + omit 'todo' unless system 'cd', %i[out err in] => :close - def test_public_methods - Object.new.public_methods - Object.new.public_methods true - Object.new.public_methods false + with_string 'cd' do |cmd| + assert_send_type '(string) -> String', + Kernel, :`, cmd + end end - def test_public_send - Object.new.public_send(:inspect) - Object.new.public_send('inspect') - Object.new.public_send(:public_send, :inspect) - Object.new.public_send(:tap) { 1 } - Object.new.public_send(:tap) { |this| this } + def test_abort + omit 'todo' end - def test_remove_instance_variable - obj = Object.new - obj.instance_eval do - @foo = 1 - @bar = 2 - end - - obj.remove_instance_variable(:@foo) - obj.remove_instance_variable('@bar') + def test_at_exit + omit 'todo' end - def test_send - Object.new.send(:inspect) - Object.new.send('inspect') - Object.new.send(:public_send, :inspect) - Object.new.send(:tap) { 1 } - Object.new.send(:tap) { |this| this } + def test_autoload + omit 'todo' end - def test_singleton_class - Object.new.singleton_class + def test_autoload? + omit 'todo' end - def test_singleton_method - o = Object.new - def o.x - end - o.singleton_method :x - o.singleton_method 'x' + def test_binding + assert_send_type '() -> Binding', + Kernel, :binding end - def test_singleton_methods - o = Object.new - def o.x + def test_exit + with_int.and_chain(with_bool) do |status| + exit status + rescue SystemExit + assert true + else + flunk '`exit` should raise `SystemExit`' end - o.singleton_methods end - if Kernel.method_defined?(:taint) - def test_taint - Object.new.taint - Object.new.untrust - end - - def test_tainted? - Object.new.tainted? - Object.new.untrusted? - end + def test_exit! + omit 'todo' end - def test_tap - Object.new.tap do |this| - this - end + def test_fail(method = :fail) + omit 'todo' end - def test_to_s - Object.new.to_s + def test_raise + test_fail :raise end - if Kernel.method_defined?(:taint) - def test_untaint - Object.new.untaint - Object.new.trust + def test_format(method = :format) + with_string '%s' do |fmt| + assert_send_type '(string, *untyped) -> String', + Kernel, method, fmt, 'hello, world!' end end - def test_Array - Array(nil) - - # We add the `.first.whatever` tests to make sure that we're being returned the right type. - Array([1,2,3]).first.even? - Array(ToArray.new(1,2)).first.even? - Array(ToA.new(1,2)).first.even? - Array(1..4).first.even? - Array({34 => 'hello'}).first.first.even? - - Array('foo').first.upcase - Array(['foo']).first.upcase + def test_sprintf + test_format :sprintf end - def test_Complex - Complex(1.3).real? - Complex(1.3, exception: true).real? - Complex(1.3, exception: false)&.real? - Complex(1.3, exception: $VERBOSE)&.real? # `$VERBOSE` is an undecidable-at-compile-time bool. - - Complex('1+2i') - Complex(1r) - Complex(Class.new(Numeric).new) - - # The `Kernel#Complex` function is the only place in the entire stdlib that uses `.to_c` - def (obj = BasicObject.new).to_c - 1+3i - end - Complex(obj) - - Complex(1.3, '1i') - Complex(Class.new(Numeric).new, "1") + def test_gets + omit 'todo' end - def test_Float - Float(42).infinite? - Float(42, exception: true).real? - Float(42, exception: false)&.real? - Float(42, exception: $VERBOSE)&.real? # `$VERBOSE` is an undecidable-at-compile-time bool. - - Float(1.4) - Float('1.4') - Float(ToF.new) + def test_global_variables + assert_send_type '() -> Array[Symbol]', + Kernel, :global_variables end - def test_Hash - Hash(nil) - Hash([]) - - Hash({key: 1}) - Hash(ToHash.new) + def test_load + omit 'todo' end - def test_Integer - Integer(42).even? - Integer(42, exception: true).even? - Integer(42, exception: false)&.even? - Integer(42, exception: $VERBOSE)&.even? # `$VERBOSE` is an undecidable-at-compile-time bool. - - Integer(2.3) - Integer(ToInt.new) - Integer(ToI.new) - - Integer('2').even? - Integer('2', exception: true).even? - Integer('2', exception: false)&.even? - Integer('2', exception: $VERBOSE)&.even? # `$VERBOSE` is an undecidable-at-compile-time bool. - - Integer('11', 2) - Integer(ToStr.new('11'), ToInt.new(12)) + def test_loop + assert_send_type '() -> Enumerator[nil, bot]', + Kernel, :loop + assert_send_type '() { () -> void } -> bot', + Kernel, :loop do break end end - # These two classes are required to for `test_Rational`, and the `Class.new(Numeric)` construct - # doesn't type check them properly (yet.) - class Rational_RationalDiv < Numeric - def /(numeric) "Hello!" end - end - class Rational_OneCase < Numeric - def __unique_method_name__; 34 end + def test_open + omit 'todo' end - def test_Rational - Rational(42).integer? - Rational(42, exception: true).integer? - Rational(42, exception: false)&.integer? - Rational(42, exception: $VERBOSE)&.integer? # `$VERBOSE` is an undecidable-at-compile-time bool. - - def (test_rational = BasicObject.new).to_r - 1r - end - - Rational(ToInt.new) - Rational(test_rational) - - Rational(42.0, 3) - Rational('42.0', 3, exception: true) - Rational(ToInt.new, test_rational) - Rational(test_rational, ToInt.new, exception: false) - - rational_div = Rational_RationalDiv.new - # `Rational` ignores `exception:` in the `_RationalDiv` variant. - Rational(rational_div, Class.new(Numeric).new).upcase - Rational(rational_div, Class.new(Numeric).new, exception: true).upcase - Rational(rational_div, Class.new(Numeric).new, exception: false).upcase - Rational(rational_div, Class.new(Numeric).new, exception: $VERBOSE).upcase - - one_case = Rational_OneCase.new - # `Rational` also ignores `exception:` in the `(Numeric, 1)` variant. - Rational(one_case, 1).__unique_method_name__ - Rational(one_case, 1, exception: true).__unique_method_name__ - Rational(one_case, 1, exception: false).__unique_method_name__ - Rational(one_case, 1, exception: $VERBOSE).__unique_method_name__ + def capture_stdout(&block) + old_stdout = $stdout + ($stdout = Writer.new).tap(&block) + ensure + $stdout = old_stdout end - def test_String - String('foo') - String([]) - String(nil) - - String(ToS.new) - String(ToStr.new) + def test_print + capture_stdout do + assert_send_type '(*_ToS) -> nil', + Kernel, :print, 1, Object.new, 1r, :hello_world + end end - def test___callee__ - __callee__ - end + def test_printf + capture_stdout do + assert_send_type '() -> nil', + Kernel, :printf + assert_send_type '(String, *untyped) -> nil', + Kernel, :printf, '%s', 'hello world' + end - def test___dir__ - __dir__ + with_string '%s' do |fmt| + assert_send_type '(_Writer, string, *untyped) -> nil', + Kernel, :printf, Writer.new, fmt, 'hello, world!' + end end - def test___method__ - __method__ + def test_proc + assert_send_type '() { (*untyped, **untyped) -> untyped } -> Proc', + Kernel, :proc do end end - def test_backtick - `echo 1` + def test_lambda + assert_send_type '() { (*untyped, **untyped) -> untyped } -> Proc', + Kernel, :lambda do end end - def test_abort - begin - abort - rescue SystemExit - end - - begin - abort 'foo' - rescue SystemExit + def test_putc + capture_stdout do + assert_send_type '(String) -> String', + Kernel, :putc, '&' + with_int 38 do |int| + assert_send_type '[T < _ToInt] (T) -> T', + Kernel, :putc, int + end end + end - begin - abort ToStr.new - rescue SystemExit + def test_puts + capture_stdout do + assert_send_type '(*_ToS) -> nil', + Kernel, :puts, 1, Object.new, 1r, :hello_world end end - def test_at_exit - at_exit { 'foo' } - end + def test_p + inspectable = BlankSlate.new.__with_object_methods(:inspect) - def test_autoload - autoload 'FooBar', 'fname' - autoload :FooBar, 'fname' + capture_stdout do + assert_send_type '() -> nil', + Kernel, :p + assert_send_type '[T < _Inspect] (T) -> T', + Kernel, :p, inspectable + assert_send_type '(_Inspect, _Inspect, *_Inspect) -> Array[_Inspect]', + Kernel, :p, inspectable, inspectable + end end - def test_autoload? - autoload? 'FooBar' - autoload? :FooBarBaz + def test_pp + omit 'todo' end - def test_binding - binding + def test_rand + omit 'todo' end - def test_exit - begin - exit - rescue SystemExit - end - - begin - exit 1 - rescue SystemExit - end + def test_readline + old_stdin = $stdin - begin - exit ToInt.new - rescue SystemExit - end + $stdin = BlankSlate.new + file = ::File.open(__FILE__) - begin - exit true - rescue SystemExit + ::Kernel.instance_method(:define_singleton_method).bind_call($stdin, :readline) do |*a, **k| + file.readline(*a, **k) + rescue EOFError # `__FILE__` isn't large enough to be read in one pass. + file.rewind + retry end - begin - exit false - rescue SystemExit - end - end + assert_send_type '() -> String', + Kernel, :readline + + with_string("\n").and_nil do |sep| + assert_send_type '(string?) -> String', + Kernel, :readline, sep - def test_exit! - # TODO - end + with_boolish do |chomp| + assert_send_type '(string?, chomp: boolish) -> String', + Kernel, :readline, sep, chomp: chomp + end - def test_fail - begin - fail - rescue RuntimeError - end + with_int(10).and_nil do |limit| + assert_send_type '(string?, int?) -> String', + Kernel, :readline, sep, limit - begin - fail 'error' - rescue RuntimeError - end - - begin - fail 'error', cause: nil - rescue RuntimeError + with_boolish do |chomp| + assert_send_type '(string?, int?, chomp: boolish) -> String', + Kernel, :readline, sep, limit, chomp: chomp + end + end end - begin - fail 'error', cause: RuntimeError.new("oops!") - rescue RuntimeError - end + with_int(10).and_nil do |limit| + assert_send_type '(int?) -> String', + Kernel, :readline, limit - test_error = Class.new(StandardError) - begin - fail test_error - rescue test_error + with_boolish do |chomp| + assert_send_type '(int?, chomp: boolish) -> String', + Kernel, :readline, limit, chomp: chomp + end end + ensure + file.close + $stdin = old_stdin + end - begin - fail test_error, 'a' - rescue test_error - end + def test_readlines + old_stdin = $stdin - begin - fail test_error, ToS.new, ['1.rb, 2.rb'] - rescue test_error - end + $stdin = BlankSlate.new + file = ::File.open(__FILE__) - begin - fail test_error, 'b', '1.rb' - rescue test_error + ::Kernel.instance_method(:define_singleton_method).bind_call($stdin, :readlines) do |*a, **k| + file.readlines(*a, **k) + rescue EOFError # `__FILE__` isn't large enough to be read in one pass. + file.rewind + retry end - begin - fail test_error, 'b', nil - rescue test_error - end + assert_send_type '() -> Array[String]', + Kernel, :readlines + + with_string("\n").and_nil do |sep| + assert_send_type '(string?) -> Array[String]', + Kernel, :readlines, sep - begin - fail test_error, 'b', cause: RuntimeError.new("?") - rescue test_error - end + with_boolish do |chomp| + assert_send_type '(string?, chomp: boolish) -> Array[String]', + Kernel, :readlines, sep, chomp: chomp + end - begin - fail test_error, 'b', cause: nil - rescue test_error - end + with_int(10).and_nil do |limit| + assert_send_type '(string?, int?) -> Array[String]', + Kernel, :readlines, sep, limit - begin - fail test_error.new('a') - rescue test_error + with_boolish do |chomp| + assert_send_type '(string?, int?, chomp: boolish) -> Array[String]', + Kernel, :readlines, sep, limit, chomp: chomp + end + end end - begin - fail test_error.new('a'), foo: 1, bar: 2, baz: 3, cause: RuntimeError.new("?") - rescue test_error - end + with_int(10).and_nil do |limit| + assert_send_type '(int?) -> Array[String]', + Kernel, :readlines, limit - exception_container = Class.new do - define_method :exception do |arg = 'a'| - test_error.new(arg) + with_boolish do |chomp| + assert_send_type '(int?, chomp: boolish) -> Array[String]', + Kernel, :readlines, limit, chomp: chomp end end + ensure + file.close + $stdin = old_stdin + end - begin - fail exception_container.new - rescue test_error - end - - begin - fail exception_container.new, 14 - rescue test_error + def test_require + with_path File.join(__dir__, 'util', 'valid.rb') do |path| + assert_send_type '(path) -> bool', + Kernel, :require, path end end - def test_format - format 'x' - format '%d', 1 - sprintf '%d%s', 1, 2 + def test_require_relative + with_path File.join('util', 'valid.rb') do |path| + assert_send_type '(path) -> bool', + Kernel, :require_relative, path + end end - def test_gets - # TODO + def test_select + omit 'todo' end - def test_global_variables - global_variables + def test_sleep + omit 'todo' end - def test_load - Dir.mktmpdir do |dir| - path = File.join(dir, "foo.rb") - - File.write(path, "class Foo; end") - - load(path) - load(path, true) - load(path, false) - load(path, Module.new) - end + def test_syscall + # There's no real way to typecheck this, as syscalls aren't portable at all. end - def test_loop - loop { break } - loop + def test_test + omit 'todo' end - def test_open - open(File.expand_path(__FILE__)).close - open(File.expand_path(__FILE__), 'r').close - open(File.expand_path(__FILE__)) do |f| - f.read - end + def test_throw + omit 'todo' end - def test_print - print - print 1 - print 'a', 2 - print ToS.new + def test_warn + omit 'todo' end +end - def test_printf - File.open('/dev/null', 'w') do |io| - printf io, 'a' - printf io, '%d', 2 - end +class KernelInstanceTest < Test::Unit::TestCase + include TypeAssertions - printf - printf "123" - printf "%s%d%f", "A", 2, 3.0 + testing '::Kernel' - def (writer = Object.new).write(*) end - printf writer, ToStr.new("%s%d"), '1', 2 + def test_nmatch + omit 'todo' end - def test_proc - proc {} + def test_cmp + omit 'todo' end - def test_lambda - lambda {} + def test_eqq + omit 'todo' end - def test_putc - putc 1 - putc 'a' - putc ToInt.new + def test_class + omit 'todo' end - def test_puts - puts - puts 1, nil, false, "yes!", ToS.new + def test_clone + omit 'todo' end - def test_p - p - p 1 - p 'a', 2 - - def (obj = BasicObject.new).inspect - "foo" - end + def test_define_singleton_method + omit 'todo' + end - p obj + def test_display + omit 'todo' end - def test_pp - pp - pp 1 - pp 'a', 2 + def test_dup + omit 'todo' + end - pp Object.new + def test_enum_for(method = :enum_for) + omit 'todo' end - def test_rand - rand - rand(10) - rand(1..10) - rand(1.0..10.0) + def test_to_enum + test_enum_for :to_enum end - def test_readline - # TODO + def test_eql? + omit 'todo' end - def test_readlines - # TODO + def test_extend + omit 'todo' end - def test_require - # TODO + def test_freeze + omit 'todo' end - def test_require_relative - # TODO + def test_frozen? + omit 'todo' end - def test_select - # TODO + def test_hash + omit 'todo' end - def test_sleep - sleep 0 + def test_inspect + omit 'todo' + end - sleep 0.01 + def test_instance_of? + omit 'todo' + end - o = Object.new - def o.divmod(i) - [0.001, 0.001] - end - sleep o + def test_instance_variable_defined? + omit 'todo' end - def test_syscall - # TODO + def test_instance_variable_get + omit 'todo' end - def test_test - test ?r, File.expand_path(__FILE__) - test ?r.ord, File.expand_path(__FILE__) - test ?s, File.expand_path(__FILE__) + def test_instance_variable_set + omit 'todo' + end - File.open(File.expand_path(__FILE__)) do |f| - test ?r, f - test ?=, f, f - end + def test_instance_variables + omit 'todo' end - def test_warn - warn - warn 'foo' - warn 'foo', 'bar' - warn 'foo', uplevel: 1 - warn ToS.new, uplevel: ToInt.new - warn ToS.new, uplevel: nil + def test_is_a?(method = :is_a?) + omit 'todo' + end - omit_if(RUBY_VERSION < "3.0") + def test_kind_of? + test_is_a? :kind_of? + end - warn 'foo', uplevel: 1, category: :deprecated - warn 'foo', uplevel: 1, category: nil + def test_itself + omit 'todo' end - def test_exec - # TODO + def test_method + omit 'todo' end - def test_system - # TODO + def test_methods + omit 'todo' end - def test_operators - if RUBY_VERSION < "3.2.0" - Object.new !~ 123 - end + def test_nil? + omit 'todo' + end - Object.new <=> 123 - Object.new <=> Object.new + def test_object_id + omit 'todo' + end - Object.new === false + def test_private_methods + omit 'todo' end - def test_eql - Object.new.eql?(1) + def test_protected_methods + omit 'todo' end - def test_frozen - Object.new.frozen? + def test_public_method + omit 'todo' end - def test_itself - Object.new.itself + def test_public_methods + omit 'todo' end - def test_kind_of? - Object.new.kind_of?(String) + def test_public_send + omit 'todo' end - def test_object_id - Object.new.object_id + def test_remove_instance_variable + omit 'todo' end def test_respond_to? - obj = Object.new + omit 'todo' + end - obj.respond_to?(:to_s) - obj.respond_to?('to_s') - obj.respond_to?('to_s', true) + def test_send + omit 'todo' end - if Kernel.method_defined?(:taint) - def test_taint - obj = Object.new + def test_singleton_class + omit 'todo' + end - obj.taint - obj.tainted? - obj.untaint - end + def test_singleton_method + omit 'todo' end - def test_yield_self - obj = Object.new + def test_singleton_methods + omit 'todo' + end - obj.yield_self { } - obj.then { } + def test_tap + omit 'todo' end -end -class KernelInstanceTest < Test::Unit::TestCase - include TypeAssertions + def test_to_s + omit 'todo' + end - testing "::Kernel" + def test_yield_self(method = :yield_self) + omit 'todo' + end - def test_define_singleton_method - obj = Object.new - - assert_send_type( - "(::Symbol) { () -> void } -> Symbol", - obj, :define_singleton_method, - :foo - ) do end - - assert_send_type( - "(::Symbol, ::Proc) -> Symbol", - obj, :define_singleton_method, - :bar, - -> {} - ) - - assert_send_type( - "(::Symbol, ::Method) -> Symbol", - obj, :define_singleton_method, - :bar, - obj.method(:to_s) - ) - - assert_send_type( - "(::Symbol, ::UnboundMethod) -> Symbol", - obj, :define_singleton_method, - :bar, - Object.instance_method(:to_s) - ) + def test_then + test_yield_self :then end def test_respond_to_missing? - obj = Object.new - - # The default implementation always returns `false` regardless of the args, - # let alone their types; though overrides only have to support Symbol + bool - assert_send_type( - "(::Symbol, bool) -> bool", - obj, :respond_to_missing?, :to_s, true - ) + omit 'todo' end def test_initialize_copy - assert_send_type( - "(self) -> self", - Object.new, :initialize_copy, Object.new - ) + omit 'todo' end def test_initialize_clone - assert_send_type( - "(self) -> self", - Object.new, :initialize_clone, Object.new - ) - - assert_send_type( - "(self, freeze: bool) -> self", - Object.new, :initialize_clone, Object.new, freeze: true - ) + omit 'todo' end def test_initialize_dup - assert_send_type( - "(self) -> self", - Object.new, :initialize_dup, Object.new - ) + omit 'todo' end end diff --git a/test/stdlib/test_helper.rb b/test/stdlib/test_helper.rb index 0851d5fbe..aec0d7a92 100644 --- a/test/stdlib/test_helper.rb +++ b/test/stdlib/test_helper.rb @@ -168,64 +168,106 @@ def if_ruby31(&block) end module WithAliases + class WithEnum + include Enumerable + + def initialize(enum) = @enum = enum + + def each(&block) = @enum.each(&block) + + def and_nil(&block) + and_chain(nil, &block) + end + + def and_chain(*args, &block) + return WithEnum.new chain(args) unless block_given? + each(&block) + args.each do |arg| + if WithEnum === arg || Enumerable === arg + arg.each(&block) + else + block.call(arg) + end + end + end + end + def with_int(value = 3) - return to_enum(__method__, value) unless block_given? + return WithEnum.new to_enum(__method__, value) unless block_given? + yield value + yield ToInt.new(value) + end + + def with_int2(value = 3) + return WithEnum.new to_enum(__method__, value) unless block_given? yield value yield ToInt.new(value) end def with_float(value = 0.1) - return to_enum(__method__, value) unless block_given? + return WithEnum.new to_enum(__method__, value) unless block_given? yield value yield ToF.new(value) end - def with_string(value = "") - return to_enum(__method__, value) unless block_given? + def with_string(value = '') + return WithEnum.new to_enum(__method__, value) unless block_given? yield value yield ToStr.new(value) end def with_array(*elements) - return to_enum(__method__, *elements) unless block_given? + return WithEnum.new to_enum(__method__, *elements) unless block_given? yield elements yield ToArray.new(*elements) end def with_hash(hash = {}) - return to_enum(__method__, hash) unless block_given? + return WithEnum.new to_enum(__method__, hash) unless block_given? yield hash yield ToHash.new(hash) end def with_io(io = $stdout) - return to_enum(__method__, io) unless block_given? + return WithEnum.new to_enum(__method__, io) unless block_given? yield io yield ToIO.new(io) end def with_path(path = "/tmp/foo.txt", &block) - return to_enum(__method__, path) unless block_given? + return WithEnum.new to_enum(__method__, path) unless block_given? with_string(path, &block) block.call ToPath.new(path) end def with_encoding(encoding = Encoding::UTF_8, &block) - return to_enum(__method__, encoding) unless block_given? + return WithEnum.new to_enum(__method__, encoding) unless block_given? block.call encoding with_string(encoding.to_s, &block) end def with_interned(value = :&, &block) - return to_enum(__method__, value) unless block_given? + return WithEnum.new to_enum(__method__, value) unless block_given? with_string(value.to_s, &block) block.call value.to_sym end + + def with_bool + return WithEnum.new to_enum(__method__) unless block_given? + yield true + yield false + end + + def with_boolish(&block) + return WithEnum.new to_enum(__method__, value) unless block_given? + with_bool(&block) + [nil, 1, Object.new, BlankSlate.new, "hello, world!"].each(&block) + end end module TypeAssertions @@ -582,6 +624,17 @@ def to_f end end +class ToC < BlankSlate + def initialize(value = 1i) + @value = value + end + + def to_c + @value + end +end + + class ToStr < BlankSlate def initialize(value = "") @value = value diff --git a/test/stdlib/util/valid.rb b/test/stdlib/util/valid.rb new file mode 100644 index 000000000..9874acb12 --- /dev/null +++ b/test/stdlib/util/valid.rb @@ -0,0 +1,2 @@ +# this just needs to be a valid ruby file, so we can import it. +1 + 2 From d8913cd1dc380e320ad529b4ce6d92ce67690794 Mon Sep 17 00:00:00 2001 From: SamW Date: Tue, 7 Nov 2023 13:46:19 -0800 Subject: [PATCH 2/6] more updates --- core/kernel.rbs | 14 +- test/stdlib/Kernel_test.rb | 304 +++++++++++++++++++++++++++++++------ test/stdlib/test_helper.rb | 4 +- 3 files changed, 272 insertions(+), 50 deletions(-) diff --git a/core/kernel.rbs b/core/kernel.rbs index 046e3f960..fe044abd6 100644 --- a/core/kernel.rbs +++ b/core/kernel.rbs @@ -1835,6 +1835,8 @@ module Kernel : BasicObject # # never get here # + type redirect_fd = Integer | :in | :out | :err | IO | String | [ String ] | [ String, string | int ] | [ String, string | int, int ] | [ :child, int ] | :close + # def self?.exec: ( # ?Hash[string, string]? env, # [string, string] | string prog_arg0, @@ -2261,7 +2263,7 @@ module Kernel : BasicObject # When you define #<=>, you can include Comparable to gain the methods #<=, #<, # #==, #>=, #> and #between?. # - def <=>: (untyped other) -> 0? + def <=>: (untyped other) -> 0? # where self < _Eq[untyped, untyped] # - # Replaces the current process by running the given external *command*, which - # can take one of the following forms: - # - # `exec(commandline)` - # : command line string which is passed to the standard shell - # `exec(cmdname, arg1, ...)` - # : command name and one or more arguments (no shell) - # `exec([cmdname, argv0], arg1, ...)` - # : command name, `argv[0]` and zero or more arguments (no shell) - # - # - # In the first form, the string is taken as a command line that is subject to - # shell expansion before being executed. - # - # The standard shell always means `"/bin/sh"` on Unix-like systems, otherwise, - # `ENV["RUBYSHELL"]` or `ENV["COMSPEC"]` on Windows and similar. The command is - # passed as an argument to the `"-c"` switch to the shell, except in the case of - # `COMSPEC`. - # - # If the string from the first form (`exec("command")`) follows these simple - # rules: - # - # * no meta characters, - # * not starting with shell reserved word or special built-in, - # - # - # Ruby invokes the command directly without shell. - # - # You can force shell invocation by adding ";" to the string (because ";" is a - # meta character). - # - # Note that this behavior is observable by pid obtained (return value of spawn() - # and IO#pid for IO.popen) is the pid of the invoked command, not shell. - # - # In the second form (`exec("command1", "arg1", ...)`), the first is taken as a - # command name and the rest are passed as parameters to command with no shell - # expansion. - # - # In the third form (`exec(["command", "argv0"], "arg1", ...)`), starting a - # two-element array at the beginning of the command, the first element is the - # command to be executed, and the second argument is used as the `argv[0]` - # value, which may show up in process listings. - # - # In order to execute the command, one of the `exec(2)` system calls are used, - # so the running command may inherit some of the environment of the original - # program (including open file descriptors). - # - # This behavior is modified by the given `env` and `options` parameters. See - # ::spawn for details. - # - # If the command fails to execute (typically Errno::ENOENT when it was not - # found) a SystemCallError exception is raised. - # - # This method modifies process attributes according to given `options` before - # `exec(2)` system call. See ::spawn for more details about the given `options`. - # - # The modified attributes may be retained when `exec(2)` system call fails. - # - # For example, hard resource limits are not restorable. - # - # Consider to create a child process using ::spawn or Kernel#system if this is - # not acceptable. - # - # exec "echo *" # echoes list of files in current directory - # # never get here - # - # exec "echo", "*" # echoes an asterisk - # # never get here - # - type redirect_fd = Integer | :in | :out | :err | IO | String | [ String ] | [ String, string | int ] | [ String, string | int, int ] | [ :child, int ] | :close - # def self?.exec: ( - # ?Hash[string, string]? env, - # [string, string] | string prog_arg0, - # *string args, - # ?Hash[Symbol | redirect_key, bool? | int | path | redirect_val] opts, - # ) -> bot - # | ( - # ?Hash[string, string]? env, - # [string, string] | string prog_arg0, - # *string args, - # ?pgroup: int | bool?, - # ?unsetenv_others: bool?, - # ?chdir: path, - # ?umask: int, - # ?close_others: bool?, - # ?in: redirect_val, - # ?out: redirect_val, - # ?err: redirect_val, - # ?uid: string | int, - # ?gid: string | int, - # ?exception: bool?, - # **redirect_key => redirect_val opts - # ) -> bot - - # def self?.exec - # : (string prog, *string args, ?Hash[Symbol | redirect_key, bool? | int | path | redirect_val] opts) -> bot - # | ([string, string] prog_arg0, *string args, ?Hash[Symbol | redirect_key, bool? | int | path | redirect_val] opts) -> bot - # : ( - # [string, string] | string prog_maybe_arg0, - # *string args, - # **(redirect_key => redirect_key) - # # ?Hash[Symbol | redirect_key, bool? | int | path | string | redirect_val] opts - # ) -> bot - # | ([string, string] prog_arg0, *string args, ?Hash[Symbol | redirect_key, bool? | int | path | string | redirect_val] opts) -> bot - - # ?Hash[string, string?] env, - # ([string, string] | string) maybe_prog_also_arg0, # Technically is an `array[string]` that's convertible into length 2. - # *string args, - # ?Hash[Symbol | redirect_key, bool? | int | path | string | redirect_val] opts) -> bot - # | ( - # ?Hash[string, string?] env, - # ([string, string] | string) maybe_prog_also_arg0, # Technically is an `array[string]` that's convertible into length 2. - # *string args, - - # ?Hash[redirect_key, redirect_val] opts - # ?pgroup: bool? | int, - # ?unsetenv_others: bool?, - # ?chdir: path, - # ?umask: int, - # ?close_others: bool?, - # ?in: redirect_val, - # ?out: redirect_val, - # ?err: redirect_val, - # ?uid: string | int, - # ?gid: string | int, - # ?exception: bool?) -> bot - - # # TODO: HAVE_SETRLIMIT - # type redirect_key = :in | :out | :err | Integer | io - # type redirect_val = :close | :in | :out | :err | File | Integer | String | io | [:child, redirect_key] | [path, String | int?] | [path, String | int?, int] - - # def self?.exec - # # : (String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String) -> bot - # # | (Hash[string, string?] env, String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String) -> bot - # : ( - # ?Hash[string, string?] env, - # ([string, string] | string) maybe_prog_also_arg0, # Technically is an `array[string]` that's convertible into length 2. - # *string args, - # ?Hash[Symbol | redirect_key, bool? | int | path | string | redirect_val] opts) -> bot - # | ( - # ?Hash[string, string?] env, - # ([string, string] | string) maybe_prog_also_arg0, # Technically is an `array[string]` that's convertible into length 2. - # *string args, - - # ?Hash[redirect_key, redirect_val] opts - # ?pgroup: bool? | int, - # ?unsetenv_others: bool?, - # ?chdir: path, - # ?umask: int, - # ?close_others: bool?, - # ?in: redirect_val, - # ?out: redirect_val, - # ?err: redirect_val, - # ?uid: string | int, - # ?gid: string | int, - # ?exception: bool?) -> bot - - # # TODO: HAVE_SETRLIMIT - # type redirect_key = :in | :out | :err | Integer | io - # type redirect_val = :close | :in | :out | :err | File | Integer | String | io | [:child, redirect_key] | [path, String | nil | int] | [path, String | nil | int, int] - #