Skip to content

Commit

Permalink
Merge pull request #1686 from sampersand/swesterman/23-12-18/add-_Tim…
Browse files Browse the repository at this point in the history
…eout-type

Add _Timeout type
  • Loading branch information
soutaro authored Dec 21, 2023
2 parents 250a7e2 + c7a7fdf commit 0b0960f
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 35 deletions.
13 changes: 10 additions & 3 deletions core/io.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,14 @@ class IO < Object
# -->
# Get the internal timeout duration or nil if it was not set.
#
def timeout: () -> Numeric?
def timeout: () -> io_timeout

# The type used for timeouts in `IO`.
#
# Technically, this type should be `Time::_Timeout?`. However, in the vast majority of use-cases,
# people aren't going to pass their own `_Timeout` in, so `Numeric` is returned for ergonomics
# (eg `io.timeout += 10`).
type io_timeout = Numeric?

# <!--
# rdoc-file=io.c
Expand All @@ -2110,7 +2117,7 @@ class IO < Object
# effort to prevent an application from hanging on slow I/O operations, such as
# those that occur during a slowloris attack.
#
def timeout=: (Numeric? duration) -> void
def timeout=: (io_timeout duration) -> void

# <!--
# rdoc-file=io.c
Expand Down Expand Up @@ -2945,7 +2952,7 @@ class IO < Object
# ping
#
def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ]
| [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Numeric? timeout) -> [ Array[X], Array[Y], Array[Z] ]?
| [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]?

# <!--
# rdoc-file=io.c
Expand Down
8 changes: 4 additions & 4 deletions core/io/wait.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class IO
#
# You must require 'io/wait' to use this method.
#
def wait: (Integer events, ?Numeric timeout) -> (Integer | false | nil)
| (?Numeric? timeout, *wait_mode mode) -> (self | true | false)
def wait: (Integer events, ?Time::_Timeout timeout) -> (Integer | false | nil)
| (?Time::_Timeout? timeout, *wait_mode mode) -> (self | true | false)

type wait_mode = :read | :r | :readable | :write | :w | :writable | :read_write | :rw | :readable_writable

Expand All @@ -54,7 +54,7 @@ class IO
#
# You must require 'io/wait' to use this method.
#
def wait_readable: (?Numeric? timeout) -> boolish
def wait_readable: (?Time::_Timeout? timeout) -> boolish

# <!--
# rdoc-file=ext/io/wait/wait.c
Expand All @@ -66,5 +66,5 @@ class IO
#
# You must require 'io/wait' to use this method.
#
def wait_writable: (?Numeric? timeout) -> boolish
def wait_writable: (?Time::_Timeout? timeout) -> boolish
end
5 changes: 3 additions & 2 deletions core/kernel.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,7 @@ module Kernel : BasicObject
# (snipped)
# ping
#
def self?.select: (::Array[IO] read, ?::Array[IO] write, ?::Array[IO] error, ?Integer timeout) -> ::Array[String]
def self?.select: (::Array[IO] read, ?::Array[IO] write, ?::Array[IO] error, ?Time::_Timeout timeout) -> ::Array[String]

# <!--
# rdoc-file=process.c
Expand All @@ -1581,8 +1581,9 @@ module Kernel : BasicObject
# Time.new # => 2008-03-08 19:56:22 +0900
#
def self?.sleep: (?nil) -> bot
| (Integer | Float | _Divmod duration) -> Integer
| (Time::_Timeout duration) -> Integer

%a{steep:deprecated}
interface _Divmod
def divmod: (Numeric) -> [ Numeric, Numeric ]
end
Expand Down
2 changes: 1 addition & 1 deletion core/thread.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ class Thread::ConditionVariable < Object
#
# Returns the slept result on `mutex`.
#
def wait: (Thread::Mutex mutex, ?Integer | Float? timeout) -> Integer?
def wait: (Thread::Mutex mutex, ?Time::_Timeout? timeout) -> Integer?
end

# <!-- rdoc-file=thread_sync.c -->
Expand Down
20 changes: 20 additions & 0 deletions core/time.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,26 @@
# You can define this method per subclasses, or on the toplevel Time class.
#
class Time < Object
# A type that's used for timeouts.
#
# All numeric types implement this, but custom classes can also implement it if desired.
#
# Usage of `Time::_Timeout` is fairly common throughout the stdlib, such as in `Kernel#sleep`,
# `IO#timeout=`, and `TCPSocket#new`'s `connet_timeout` field.
interface _Timeout
# Returns `[seconds, nanoseconds]`.
#
# The `seconds` should be a whole number of seconds, whereas the `nanoseconds` should be smaller
# than one. For example, `3.125.divmod(1)` would yield `[3, 0.125]`
def divmod: (1) -> [int, _TimeoutNSecs]
end

# The nanoseconds part of `Time::_Timeout`'s return value. See it for details
interface _TimeoutNSecs
# Convert `self` into a whole number of seconds.
def *: (1_000_000_000) -> int
end

include Comparable

# <!--
Expand Down
4 changes: 2 additions & 2 deletions stdlib/socket/0/socket.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,8 @@ class Socket < BasicSocket
# puts sock.read
# }
#
def self.tcp: (String host, Integer port, ?String local_host, ?Integer local_port, ?resolv_timeout: Numeric, ?connect_timeout: Numeric) -> instance
| (String host, Integer port, ?String local_host, ?Integer local_port, ?resolv_timeout: Numeric, ?connect_timeout: Numeric) { (instance) -> void } -> void
def self.tcp: (String host, Integer port, ?String local_host, ?Integer local_port, ?resolv_timeout: Time::_Timeout, ?connect_timeout: Time::_Timeout) -> instance
| (String host, Integer port, ?String local_host, ?Integer local_port, ?resolv_timeout: Time::_Timeout, ?connect_timeout: Time::_Timeout) { (instance) -> void } -> void

# <!--
# rdoc-file=ext/socket/lib/socket.rb
Expand Down
48 changes: 25 additions & 23 deletions test/stdlib/IO_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,29 +152,31 @@ def test_new

def test_select
if_ruby "3.0.0"..."3.2.0" do
r, w = IO.pipe
assert_send_type "(Array[IO], nil, nil, Float) -> nil",
IO, :select, [r], nil, nil, 0.5
assert_send_type "(nil, Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w]
assert_send_type "(nil, Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w], [r]
assert_send_type "(nil, Array[IO], Array[IO], Float) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w], [r], 0.5
w.write("x")
assert_send_type "(Array[IO], nil, nil, Float) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], nil, nil, 0.5
assert_send_type "(Array[IO], nil, nil, nil) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], nil, nil, nil
assert_send_type "(Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w]
assert_send_type "(Array[IO], Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w], [r]
assert_send_type "(Array[IO], Array[IO], Array[IO], Float) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w], [r], 0.5
ensure
r.close
w.close
with_timeout.and_nil do |timeout|
r, w = IO.pipe
assert_send_type "(Array[IO], nil, nil, Time::_Timeout?) -> nil",
IO, :select, [r], nil, nil, timeout
assert_send_type "(nil, Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w]
assert_send_type "(nil, Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w], [r]
assert_send_type "(nil, Array[IO], Array[IO], Time::_Timeout?) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, nil, [w], [r], timeout
w.write("x")
assert_send_type "(Array[IO], nil, nil, Time::_Timeout?) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], nil, nil, timeout
assert_send_type "(Array[IO], nil, nil, nil) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], nil, nil, nil
assert_send_type "(Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w]
assert_send_type "(Array[IO], Array[IO], Array[IO]) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w], [r]
assert_send_type "(Array[IO], Array[IO], Array[IO], Time::_Timeout?) -> [Array[IO], Array[IO], Array[IO]]",
IO, :select, [r], [w], [r], timeout
ensure
r.close
w.close
end
end
end
end
Expand Down
32 changes: 32 additions & 0 deletions test/stdlib/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,37 @@ def if_ruby31(&block)
end
end

module WithStdlibAliases
def with_timeout(seconds: 1, nanoseconds: 0)
unless block_given?
return RBS::UnitTest::Convertibles::WithAliases::WithEnum.new(
to_enum(__method__, seconds: seconds, nanoseconds: nanoseconds)
)
end

timeout_obj = RBS::UnitTest::Convertibles::BlankSlate.new.__with_object_methods(:define_singleton_method)
nanosecs_obj = RBS::UnitTest::Convertibles::BlankSlate.new.__with_object_methods(:define_singleton_method)

secs = nanosecs = nil
timeout_obj.define_singleton_method :divmod do |x|
flunk "Expected `1` for argument to divmod, got #{x.inspect}" unless 1.equal? x
[secs, nanosecs_obj]
end
nanosecs_obj.define_singleton_method :* do |x|
flunk "Expected `1000000000` for argument to *, got #{x.inspect}" unless 1000000000.equal? x
nanosecs
end

with_int seconds do |s|
secs = s
with_int nanoseconds do |ns|
nanosecs = ns
yield timeout_obj
end
end
end
end

class Writer
attr_reader :buffer

Expand Down Expand Up @@ -119,6 +150,7 @@ module TestHelper
include RBS::UnitTest::Convertibles
include RBS::UnitTest::WithAliases
include VersionHelper
include WithStdlibAliases

def self.included(base)
base.extend RBS::UnitTest::TypeAssertions::ClassMethods
Expand Down

0 comments on commit 0b0960f

Please sign in to comment.