From 02c106fb50910fac63165cd91c8eb0b288830e13 Mon Sep 17 00:00:00 2001 From: ksss Date: Sat, 10 Oct 2026 16:54:36 +0900 Subject: [PATCH 1/4] Add missing signatures to socket - Add `Socket::ResolutionError` - Add `Socket::Option` - Add `Socket.tcp_fast_fallback` and `Socket.tcp_fast_fallback=` - Fix `BasicSocket#getsockopt` to return `Socket::Option`, and let `BasicSocket#setsockopt` accept a `Socket::Option` Co-Authored-By: Claude Sonnet 5.5 --- stdlib/socket/0/basic_socket.rbs | 5 +- stdlib/socket/0/socket.rbs | 192 ++++++++++++++++++ stdlib/socket/0/socket_error.rbs | 15 ++ .../stdlib/socket/BasicSocket_sockopt_test.rb | 44 ++++ test/stdlib/socket/SocketOption_test.rb | 130 ++++++++++++ .../socket/SocketResolutionError_test.rb | 23 +++ test/stdlib/socket/Socket_test.rb | 17 ++ 7 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 test/stdlib/socket/BasicSocket_sockopt_test.rb create mode 100644 test/stdlib/socket/SocketOption_test.rb create mode 100644 test/stdlib/socket/SocketResolutionError_test.rb diff --git a/stdlib/socket/0/basic_socket.rbs b/stdlib/socket/0/basic_socket.rbs index a69132467b..f77c1fa8bd 100644 --- a/stdlib/socket/0/basic_socket.rbs +++ b/stdlib/socket/0/basic_socket.rbs @@ -237,7 +237,7 @@ class BasicSocket < IO # onoff, linger = optval.unpack "ii" # onoff = onoff == 0 ? false : true # - def getsockopt: (Symbol | Integer, Symbol | Integer) -> (Integer | boolish | String) + def getsockopt: (interned | Integer level, interned | Integer optname) -> Socket::Option # + # Returns whether the "Happy Eyeballs Version 2" fast fallback is enabled by + # default for Socket.tcp and TCPSocket.new. The initial value is `true`. + # + def self.tcp_fast_fallback: () -> bool + + # + # Sets the default of the `fast_fallback:` option for Socket.tcp and + # TCPSocket.new. Setting it to `false` disables "Happy Eyeballs Version 2" unless + # `fast_fallback:` is given explicitly. + # + def self.tcp_fast_fallback=: (bool value) -> bool + # +# Socket::Option represents a socket option (the level, option name and value +# used by `getsockopt` and `setsockopt`) together with the socket family it is +# meant for. +# +class Socket::Option + # + # Returns a new Socket::Option object. `data` is the raw option value. + # + # sockopt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")) + # p sockopt #=> # + # + def initialize: (interned | Integer family, interned | Integer level, interned | Integer optname, String data) -> void + + # + # Creates a new Socket::Option object which contains boolean as data. Actual + # size is system dependent. + # + def self.bool: (interned | Integer family, interned | Integer level, interned | Integer optname, boolish bool) -> instance + + # + # Creates a new Socket::Option object which contains a byte as data. + # + def self.byte: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance + + # + # Creates a new Socket::Option object which contains an int as data. The size + # and endian is dependent on the host. + # + def self.int: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance + + # + # Creates a new Socket::Option object for IP_MULTICAST_LOOP. + # + def self.ipv4_multicast_loop: (Integer integer) -> instance + + # + # Creates a new Socket::Option object for IP_MULTICAST_TTL. + # + def self.ipv4_multicast_ttl: (Integer integer) -> instance + + # + # Creates a new Socket::Option object for SO_LINGER. + # + # `onoff` should be an integer or a boolean. + # + # p Socket::Option.linger(true, 10) + # #=> # + # + def self.linger: (boolish | Integer onoff, Integer secs) -> instance + + # + # Returns the data in sockopt as a boolean value. + # + def bool: () -> bool + + # + # Returns the data in sockopt as an byte. + # + def byte: () -> Integer + + # + # Returns the data in sockopt as a string. + # + def data: () -> String + + # + # Returns the socket family as an integer. + # + def family: () -> Integer + + # + # Returns a string which shows sockopt in human-readable form. + # + def inspect: () -> String + + # + # Returns the data in sockopt as an int. The size and endian is dependent on + # the host. + # + def int: () -> Integer + + # + # Returns the ipv4_multicast_loop data in sockopt as an integer. + # + def ipv4_multicast_loop: () -> Integer + + # + # Returns the ipv4_multicast_ttl data in sockopt as an integer. + # + def ipv4_multicast_ttl: () -> Integer + + # + # Returns the socket level as an integer. + # + def level: () -> Integer + + # + # Returns the linger data in sockopt as a pair of boolean and integer. + # + def linger: () -> [ bool, Integer ] + + # + # Returns the socket option name as an integer. + # + def optname: () -> Integer + + alias to_s data + + # + # Calls String#unpack on sockopt.data. + # + def unpack: (String template) -> Array[untyped] +end diff --git a/stdlib/socket/0/socket_error.rbs b/stdlib/socket/0/socket_error.rbs index 02095c7feb..33d2a6fcb5 100644 --- a/stdlib/socket/0/socket_error.rbs +++ b/stdlib/socket/0/socket_error.rbs @@ -3,3 +3,18 @@ # class SocketError < StandardError end + +# +# Socket::ResolutionError is the error class for name resolution failures, such +# as those raised by `getaddrinfo`. +# +class Socket::ResolutionError < SocketError + # + # Returns the raw error code (one of the `Socket::EAI_*` constants) reported by + # the resolver. + # + def error_code: () -> Integer +end diff --git a/test/stdlib/socket/BasicSocket_sockopt_test.rb b/test/stdlib/socket/BasicSocket_sockopt_test.rb new file mode 100644 index 0000000000..1b8728d2b7 --- /dev/null +++ b/test/stdlib/socket/BasicSocket_sockopt_test.rb @@ -0,0 +1,44 @@ +require_relative "../test_helper" + +require "socket" + +class BasicSocketSockoptTest < Test::Unit::TestCase + include TestHelper + + library "socket" + testing "::BasicSocket" + + def test_getsockopt + Socket.open(:INET, :STREAM) do |socket| + assert_send_type( + "(::Symbol, ::Symbol) -> ::Socket::Option", + socket, :getsockopt, :SOCKET, :KEEPALIVE + ) + assert_send_type( + "(::Integer, ::Integer) -> ::Socket::Option", + socket, :getsockopt, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE + ) + end + end + + def test_setsockopt + Socket.open(:INET, :STREAM) do |socket| + assert_send_type( + "(::Socket::Option) -> 0", + socket, :setsockopt, Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + ) + assert_send_type( + "(::Symbol, ::Symbol, bool) -> 0", + socket, :setsockopt, :SOCKET, :KEEPALIVE, true + ) + assert_send_type( + "(::Symbol, ::Symbol, ::Integer) -> 0", + socket, :setsockopt, :SOCKET, :KEEPALIVE, 1 + ) + assert_send_type( + "(::Integer, ::Integer, ::String) -> 0", + socket, :setsockopt, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [1].pack("i") + ) + end + end +end diff --git a/test/stdlib/socket/SocketOption_test.rb b/test/stdlib/socket/SocketOption_test.rb new file mode 100644 index 0000000000..b821e98b06 --- /dev/null +++ b/test/stdlib/socket/SocketOption_test.rb @@ -0,0 +1,130 @@ +require_relative "../test_helper" + +require "socket" + +class SocketOptionSingletonTest < Test::Unit::TestCase + include TestHelper + + library "socket" + testing "singleton(::Socket::Option)" + + def test_new + assert_send_type( + "(::Symbol, ::Symbol, ::Symbol, ::String) -> ::Socket::Option", + Socket::Option, :new, :INET, :SOCKET, :KEEPALIVE, [1].pack("i") + ) + assert_send_type( + "(::String, ::Integer, ::Integer, ::String) -> ::Socket::Option", + Socket::Option, :new, "INET", Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [1].pack("i") + ) + end + + def test_bool + assert_send_type( + "(::Symbol, ::Symbol, ::Symbol, bool) -> ::Socket::Option", + Socket::Option, :bool, :INET, :SOCKET, :KEEPALIVE, true + ) + end + + def test_byte + assert_send_type( + "(::Symbol, ::Symbol, ::Symbol, ::Integer) -> ::Socket::Option", + Socket::Option, :byte, :INET, :IP, :TTL, 3 + ) + end + + def test_int + assert_send_type( + "(::Symbol, ::Symbol, ::Symbol, ::Integer) -> ::Socket::Option", + Socket::Option, :int, :INET, :SOCKET, :KEEPALIVE, 1 + ) + end + + def test_ipv4_multicast_loop + assert_send_type( + "(::Integer) -> ::Socket::Option", + Socket::Option, :ipv4_multicast_loop, 1 + ) + end + + def test_ipv4_multicast_ttl + assert_send_type( + "(::Integer) -> ::Socket::Option", + Socket::Option, :ipv4_multicast_ttl, 1 + ) + end + + def test_linger + assert_send_type( + "(bool, ::Integer) -> ::Socket::Option", + Socket::Option, :linger, true, 10 + ) + assert_send_type( + "(::Integer, ::Integer) -> ::Socket::Option", + Socket::Option, :linger, 1, 10 + ) + end +end + +class SocketOptionTest < Test::Unit::TestCase + include TestHelper + + library "socket" + testing "::Socket::Option" + + def keepalive + Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")) + end + + def test_bool + assert_send_type "() -> bool", keepalive, :bool + end + + def test_byte + assert_send_type "() -> ::Integer", Socket::Option.byte(:INET, :IP, :TTL, 3), :byte + end + + def test_data + assert_send_type "() -> ::String", keepalive, :data + end + + def test_to_s + assert_send_type "() -> ::String", keepalive, :to_s + end + + def test_family + assert_send_type "() -> ::Integer", keepalive, :family + end + + def test_inspect + assert_send_type "() -> ::String", keepalive, :inspect + end + + def test_int + assert_send_type "() -> ::Integer", keepalive, :int + end + + def test_ipv4_multicast_loop + assert_send_type "() -> ::Integer", Socket::Option.ipv4_multicast_loop(1), :ipv4_multicast_loop + end + + def test_ipv4_multicast_ttl + assert_send_type "() -> ::Integer", Socket::Option.ipv4_multicast_ttl(1), :ipv4_multicast_ttl + end + + def test_level + assert_send_type "() -> ::Integer", keepalive, :level + end + + def test_linger + assert_send_type "() -> [bool, ::Integer]", Socket::Option.linger(true, 10), :linger + end + + def test_optname + assert_send_type "() -> ::Integer", keepalive, :optname + end + + def test_unpack + assert_send_type "(::String) -> ::Array[untyped]", keepalive, :unpack, "i" + end +end diff --git a/test/stdlib/socket/SocketResolutionError_test.rb b/test/stdlib/socket/SocketResolutionError_test.rb new file mode 100644 index 0000000000..4aa2ccebba --- /dev/null +++ b/test/stdlib/socket/SocketResolutionError_test.rb @@ -0,0 +1,23 @@ +require_relative "../test_helper" + +require "socket" + +class SocketResolutionErrorTest < Test::Unit::TestCase + include TestHelper + + library "socket" + testing "::Socket::ResolutionError" + + def resolution_error + Addrinfo.getaddrinfo("nonexistent.invalid", nil) + rescue Socket::ResolutionError => error + error + end + + def test_error_code + assert_send_type( + "() -> ::Integer", + resolution_error, :error_code + ) + end +end diff --git a/test/stdlib/socket/Socket_test.rb b/test/stdlib/socket/Socket_test.rb index f024c89456..2c103ddac3 100644 --- a/test/stdlib/socket/Socket_test.rb +++ b/test/stdlib/socket/Socket_test.rb @@ -66,6 +66,23 @@ def test_tcp ) end end + + def test_tcp_fast_fallback + assert_send_type( + "() -> bool", + Socket, :tcp_fast_fallback + ) + end + + def test_tcp_fast_fallback= + original = Socket.tcp_fast_fallback + assert_send_type( + "(bool) -> bool", + Socket, :tcp_fast_fallback=, false + ) + ensure + Socket.tcp_fast_fallback = original + end end class SocketInstanceTest < Test::Unit::TestCase From 88174e3a45d64972e878d14235ea606dd9fdbaf4 Mon Sep 17 00:00:00 2001 From: ksss Date: Sat, 10 Oct 2026 17:00:11 +0900 Subject: [PATCH 2/4] Fix RBS/Layout/OverloadIndentation in BasicSocket#setsockopt Co-Authored-By: Claude Sonnet 5.5 --- stdlib/socket/0/basic_socket.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/socket/0/basic_socket.rbs b/stdlib/socket/0/basic_socket.rbs index f77c1fa8bd..eefd09af32 100644 --- a/stdlib/socket/0/basic_socket.rbs +++ b/stdlib/socket/0/basic_socket.rbs @@ -545,7 +545,7 @@ class BasicSocket < IO # sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, optval) # def setsockopt: (Socket::Option socketoption) -> 0 - | (interned | Integer level, interned | Integer optname, boolish | Integer | String optval) -> 0 + | (interned | Integer level, interned | Integer optname, boolish | Integer | String optval) -> 0 # - # Returns whether the "Happy Eyeballs Version 2" fast fallback is enabled by - # default for Socket.tcp and TCPSocket.new. The initial value is `true`. + # Returns whether Happy Eyeballs Version 2 ([RFC + # 8305](https://datatracker.ietf.org/doc/html/rfc8305)), which is provided + # starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp, is enabled or + # disabled. + # + # If true, it is enabled for TCPSocket.new and Socket.tcp. (Note: Happy Eyeballs + # Version 2 is not provided when using TCPSocket.new on Windows.) + # + # If false, Happy Eyeballs Version 2 is disabled. + # + # For details on Happy Eyeballs Version 2, see + # [Socket.tcp_fast_fallback=](rdoc-ref:Socket.tcp_fast_fallback=). # def self.tcp_fast_fallback: () -> bool # - # Sets the default of the `fast_fallback:` option for Socket.tcp and - # TCPSocket.new. Setting it to `false` disables "Happy Eyeballs Version 2" unless - # `fast_fallback:` is given explicitly. + # Enable or disable Happy Eyeballs Version 2 ([RFC + # 8305](https://datatracker.ietf.org/doc/html/rfc8305)) globally, which is + # provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp. + # + # When set to true, the feature is enabled for both TCPSocket.new + # and Socket.tcp. (Note: This feature is not available when using + # TCPSocket.new on Windows.) + # + # When set to false, the behavior reverts to that of Ruby 3.3 or earlier. + # + # The default value is true if no value is explicitly set by calling this + # method. However, when the environment variable RUBY_TCP_NO_FAST_FALLBACK=1 is + # set, the default is false. + # + # To control the setting on a per-method basis, use the fast_fallback keyword + # argument for each method. + # + # ### Happy Eyeballs Version 2 + # Happy Eyeballs Version 2 ([RFC + # 8305](https://datatracker.ietf.org/doc/html/rfc8305)) is an algorithm designed + # to improve client socket connectivity. + # It aims for more reliable and efficient connections by performing hostname + # resolution and connection attempts in parallel, instead of serially. + # + # Starting from Ruby 3.4, this method operates as follows with this algorithm: + # + # 1. Start resolving both IPv6 and IPv4 addresses concurrently. + # 2. Start connecting to the one of the addresses that are obtained first. + # If IPv4 addresses are obtained first, the method waits 50 ms for IPv6 name + # resolution to prioritize IPv6 connections. + # 3. After starting a connection attempt, wait 250 ms for the connection to be + # established. + # If no connection is established within this time, a new connection is + # started every 250 ms + # until a connection is established or there are no more candidate + # addresses. + # (Although RFC 8305 strictly specifies sorting addresses, + # this method only alternates between IPv6 / IPv4 addresses due to the + # performance concerns) + # 4. Once a connection is established, all remaining connection attempts are + # canceled. # def self.tcp_fast_fallback=: (bool value) -> bool @@ -4177,16 +4225,16 @@ class Socket::AncillaryData end # -# Socket::Option represents a socket option (the level, option name and value -# used by `getsockopt` and `setsockopt`) together with the socket family it is -# meant for. +# Socket::Option represents a socket option used by BasicSocket#getsockopt and +# BasicSocket#setsockopt. A socket option contains the socket #family, protocol +# #level, option name #optname and option value #data. # class Socket::Option # - # Returns a new Socket::Option object. `data` is the raw option value. + # Returns a new Socket::Option object. # # sockopt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")) # p sockopt #=> # @@ -4197,8 +4245,16 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - Socket::Option.bool(family, level, optname, bool) => sockopt # --> - # Creates a new Socket::Option object which contains boolean as data. Actual - # size is system dependent. + # Creates a new Socket::Option object which contains boolean as data. Actually 0 + # or 1 as int is used. + # + # require 'socket' + # + # p Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true) + # #=> # + # + # p Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false) + # #=> # # def self.bool: (interned | Integer family, interned | Integer level, interned | Integer optname, boolish bool) -> instance @@ -4208,14 +4264,21 @@ class Socket::Option # --> # Creates a new Socket::Option object which contains a byte as data. # + # p Socket::Option.byte(:INET, :SOCKET, :KEEPALIVE, 1) + # #=> # + # def self.byte: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance # - # Creates a new Socket::Option object which contains an int as data. The size - # and endian is dependent on the host. + # Creates a new Socket::Option object which contains an int as data. + # + # The size and endian is dependent on the platform. + # + # p Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + # #=> # # def self.int: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance @@ -4225,6 +4288,14 @@ class Socket::Option # --> # Creates a new Socket::Option object for IP_MULTICAST_LOOP. # + # The size is dependent on the platform. + # + # sockopt = Socket::Option.int(:INET, :IPPROTO_IP, :IP_MULTICAST_LOOP, 1) + # p sockopt.int => 1 + # + # p Socket::Option.ipv4_multicast_loop(10) + # #=> # + # def self.ipv4_multicast_loop: (Integer integer) -> instance # # Creates a new Socket::Option object for IP_MULTICAST_TTL. # + # The size is dependent on the platform. + # + # p Socket::Option.ipv4_multicast_ttl(10) + # #=> # + # def self.ipv4_multicast_ttl: (Integer integer) -> instance # - # Creates a new Socket::Option object for SO_LINGER. + # Creates a new Socket::Option object for SOL_SOCKET/SO_LINGER. # - # `onoff` should be an integer or a boolean. + # *onoff* should be an integer or a boolean. + # + # *secs* should be the number of seconds. # # p Socket::Option.linger(true, 10) - # #=> # + # #=> # # def self.linger: (boolish | Integer onoff, Integer secs) -> instance @@ -4252,7 +4330,10 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.bool => true or false # --> - # Returns the data in sockopt as a boolean value. + # Returns the data in *sockopt* as an boolean value. + # + # sockopt = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + # p sockopt.bool => true # def bool: () -> bool @@ -4260,7 +4341,10 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.byte => integer # --> - # Returns the data in sockopt as an byte. + # Returns the data in *sockopt* as an byte. + # + # sockopt = Socket::Option.byte(:INET, :SOCKET, :KEEPALIVE, 1) + # p sockopt.byte => 1 # def byte: () -> Integer @@ -4269,7 +4353,10 @@ class Socket::Option # - sockopt.data => string # - sockopt.to_s => string # --> - # Returns the data in sockopt as a string. + # returns the socket option data as a string. + # + # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).data + # #=> "\x01\x00\x00\x00" # def data: () -> String @@ -4277,7 +4364,10 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.family => integer # --> - # Returns the socket family as an integer. + # returns the socket family as an integer. + # + # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).family + # #=> 10 # def family: () -> Integer @@ -4287,14 +4377,21 @@ class Socket::Option # --> # Returns a string which shows sockopt in human-readable form. # + # p Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")).inspect + # #=> "#" + # def inspect: () -> String # - # Returns the data in sockopt as an int. The size and endian is dependent on - # the host. + # Returns the data in *sockopt* as an int. + # + # The size and endian is dependent on the platform. + # + # sockopt = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1) + # p sockopt.int => 1 # def int: () -> Integer @@ -4302,7 +4399,10 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.ipv4_multicast_loop => integer # --> - # Returns the ipv4_multicast_loop data in sockopt as an integer. + # Returns the ipv4_multicast_loop data in *sockopt* as an integer. + # + # sockopt = Socket::Option.ipv4_multicast_loop(10) + # p sockopt.ipv4_multicast_loop => 10 # def ipv4_multicast_loop: () -> Integer @@ -4310,7 +4410,10 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.ipv4_multicast_ttl => integer # --> - # Returns the ipv4_multicast_ttl data in sockopt as an integer. + # Returns the ipv4_multicast_ttl data in *sockopt* as an integer. + # + # sockopt = Socket::Option.ipv4_multicast_ttl(10) + # p sockopt.ipv4_multicast_ttl => 10 # def ipv4_multicast_ttl: () -> Integer @@ -4318,15 +4421,21 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.level => integer # --> - # Returns the socket level as an integer. + # returns the socket level as an integer. + # + # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).level + # #=> 41 # def level: () -> Integer # - # Returns the linger data in sockopt as a pair of boolean and integer. + # Returns the linger data in *sockopt* as a pair of boolean and integer. + # + # sockopt = Socket::Option.linger(true, 10) + # p sockopt.linger => [true, 10] # def linger: () -> [ bool, Integer ] @@ -4334,10 +4443,19 @@ class Socket::Option # rdoc-file=ext/socket/option.c # - sockopt.optname => integer # --> - # Returns the socket option name as an integer. + # returns the socket option name as an integer. + # + # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).optname + # #=> 2 # def optname: () -> Integer + # + # returns the socket option data as a string. + # + # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).data + # #=> "\x01\x00\x00\x00" + # alias to_s data # # Calls String#unpack on sockopt.data. # + # sockopt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")) + # p sockopt.unpack("i") #=> [1] + # p sockopt.data.unpack("i") #=> [1] + # def unpack: (String template) -> Array[untyped] end diff --git a/stdlib/socket/0/socket_error.rbs b/stdlib/socket/0/socket_error.rbs index 33d2a6fcb5..466e575c27 100644 --- a/stdlib/socket/0/socket_error.rbs +++ b/stdlib/socket/0/socket_error.rbs @@ -5,16 +5,25 @@ class SocketError < StandardError end # -# Socket::ResolutionError is the error class for name resolution failures, such -# as those raised by `getaddrinfo`. +# Socket::ResolutionError is the error class for hostname resolution. # class Socket::ResolutionError < SocketError # - # Returns the raw error code (one of the `Socket::EAI_*` constants) reported by - # the resolver. + # Returns the raw error code indicating the cause of the hostname resolution + # failure. + # + # begin + # Addrinfo.getaddrinfo("ruby-lang.org", nil) + # rescue Socket::ResolutionError => e + # if e.error_code == Socket::EAI_AGAIN + # puts "Temporary failure in name resolution." + # end + # end + # + # Note that error codes depend on the operating system. # def error_code: () -> Integer end From 8575f87dab1605320e257830505b0db49b4367ab Mon Sep 17 00:00:00 2001 From: ksss Date: Sat, 10 Oct 2026 17:15:28 +0900 Subject: [PATCH 4/4] Keep `BasicSocket#setsockopt` return type as `void` The `0` is just the success code of setsockopt(2) and is not documented as a return value, so callers should not rely on it. Co-Authored-By: Claude Sonnet 5.5 --- stdlib/socket/0/basic_socket.rbs | 4 ++-- test/stdlib/socket/BasicSocket_sockopt_test.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stdlib/socket/0/basic_socket.rbs b/stdlib/socket/0/basic_socket.rbs index eefd09af32..63552bd49c 100644 --- a/stdlib/socket/0/basic_socket.rbs +++ b/stdlib/socket/0/basic_socket.rbs @@ -544,8 +544,8 @@ class BasicSocket < IO # IPAddr.new(Socket::INADDR_ANY, Socket::AF_INET).hton # sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, optval) # - def setsockopt: (Socket::Option socketoption) -> 0 - | (interned | Integer level, interned | Integer optname, boolish | Integer | String optval) -> 0 + def setsockopt: (Socket::Option socketoption) -> void + | (interned | Integer level, interned | Integer optname, boolish | Integer | String optval) -> void #