Skip to content

Write specs for new Ruby 4.0 features and changes #1351

@eregon

Description

@eregon

ruby/spec already contains some specs for 3.4, but we should aim to cover all new features and important changes.
This will improve the test coverage of these features (and maybe discover a few bugs along the way), allow other Ruby implementations to implement the changes faster with more confidence and document clearly the new behavior.

The new specs should be within a version guard block:

ruby_version_is "3.4" do
  # New specs
end

NOTE: https://rubyreferences.github.io/rubychanges/3.4.html gives more details for many features and changes.

Notes:

  • Ractor & RubyVM related changes are removed since they are CRuby-specific.

From https://github.com/ruby/ruby/blob/master/doc/NEWS/NEWS-4.0.0.md:

NEWS for Ruby 4.0.0

This document is a list of user-visible feature changes
since the 3.4.0 release, except for bug fixes.

Note that each entry is kept to a minimum, see links for details.

Language changes

  • *nil no longer calls nil.to_a, similar to how **nil does
    not call nil.to_hash. [Feature #21047]

  • Logical binary operators (||, &&, and and or) at the
    beginning of a line continue the previous line, like fluent dot.
    The following code examples are equal:

    if condition1
       && condition2
      ...
    end

    Previously:

    if condition1 && condition2
      ...
    end
    if condition1 &&
       condition2
      ...
    end

    [Feature #20925]

Core classes updates

Note: We're only listing outstanding class updates.

Array

  • Array#rfind has been added as a more efficient alternative to array.reverse_each.find [Feature #21678]
  • Array#find has been added as a more efficient override of Enumerable#find [Feature #21678]

Binding

  • Binding#local_variables does no longer include numbered parameters.
    Also, Binding#local_variable_get, Binding#local_variable_set, and
    Binding#local_variable_defined? reject to handle numbered parameters.
    [Bug #21049]

  • Binding#implicit_parameters, Binding#implicit_parameter_get, and
    Binding#implicit_parameter_defined? have been added to access
    numbered parameters and "it" parameter. [Bug #21049]

Enumerator

  • Enumerator.produce now accepts an optional size keyword argument
    to specify the size of the enumerator. It can be an integer,
    Float::INFINITY, a callable object (such as a lambda), or nil to
    indicate unknown size. When not specified, the size defaults to
    Float::INFINITY.

    # Infinite enumerator
    enum = Enumerator.produce(1, size: Float::INFINITY, &:succ)
    enum.size  # => Float::INFINITY
    
    # Finite enumerator with known/computable size
    abs_dir = File.expand_path("./baz") # => "/foo/bar/baz"
    traverser = Enumerator.produce(abs_dir, size: -> { abs_dir.count("/") + 1 }) {
      raise StopIteration if it == "/"
      File.dirname(it)
    }
    traverser.size  # => 4

    [Feature #21701]

ErrorHighlight

  • When an ArgumentError is raised, it now displays code snippets for
    both the method call (caller) and the method definition (callee).
    [Feature #21543]

    test.rb:1:in 'Object#add': wrong number of arguments (given 1, expected 2) (ArgumentError)
    
        caller: test.rb:3
        | add(1)
          ^^^
        callee: test.rb:1
        | def add(x, y) = x + y
              ^^^
            from test.rb:3:in '<main>'
    

Fiber

  • Introduce support for Fiber#raise(cause:) argument similar to
    Kernel#raise. [Feature #21360]

Fiber::Scheduler

  • Introduce Fiber::Scheduler#fiber_interrupt to interrupt a fiber with a
    given exception. The initial use case is to interrupt a fiber that is
    waiting on a blocking IO operation when the IO operation is closed.
    [Feature #21166]

  • Introduce Fiber::Scheduler#yield to allow the fiber scheduler to
    continue processing when signal exceptions are disabled.
    [Bug #21633]

  • Reintroduce the Fiber::Scheduler#io_close hook for asynchronous IO#close.

  • Invoke Fiber::Scheduler#io_write when flushing the IO write buffer.
    [Bug #21789]

File

  • File::Stat#birthtime is now available on Linux via the statx
    system call when supported by the kernel and filesystem.
    [Feature #21205]

IO

  • IO.select accepts Float::INFINITY as a timeout argument.
    [Feature #20610]

  • A deprecated behavior, process creation by IO class methods
    with a leading |, was removed. [Feature #19630]

Kernel

  • Kernel#inspect now checks for the existence of a #instance_variables_to_inspect method,
    allowing control over which instance variables are displayed in the #inspect string:

    class DatabaseConfig
      def initialize(host, user, password)
        @host = host
        @user = user
        @password = password
      end
    
      private def instance_variables_to_inspect = [:@host, :@user]
    end
    
    conf = DatabaseConfig.new("localhost", "root", "hunter2")
    conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root">

    [Feature #21219]

  • A deprecated behavior, process creation by Kernel#open with a
    leading |, was removed. [Feature #19630]

Math

Pathname

  • Pathname has been promoted from a default gem to a core class of Ruby.
    [Feature #17473]

Proc

  • Proc#parameters now shows anonymous optional parameters as [:opt]
    instead of [:opt, nil], making the output consistent with when the
    anonymous parameter is required. [Bug #20974]

Range

  • Range#to_set now performs size checks to prevent issues with
    endless ranges. [Bug #21654]

  • Range#overlap? now correctly handles infinite (unbounded) ranges.
    [Bug #21185]

  • Range#max behavior on beginless integer ranges has been fixed.
    [Bug #21174] [Bug #21175]

Ruby

  • A new toplevel module Ruby has been defined, which contains
    Ruby-related constants. This module was reserved in Ruby 3.4
    and is now officially defined. [Feature #20884]

Set

  • Set is now a core class, instead of an autoloaded stdlib class.
    [Feature #21216]

  • Set#inspect now uses a simpler display, similar to literal arrays.
    (e.g., Set[1, 2, 3] instead of #<Set: {1, 2, 3}>). [Feature #21389]

  • Passing arguments to Set#to_set and Enumerable#to_set is now deprecated.
    [Feature #21390]

Socket

  • Socket.tcp & TCPSocket.new accepts an open_timeout keyword argument to specify
    the timeout for the initial connection. [Feature #21347]
  • When a user-specified timeout occurred in TCPSocket.new, either Errno::ETIMEDOUT
    or IO::TimeoutError could previously be raised depending on the situation.
    This behavior has been unified so that IO::TimeoutError is now consistently raised.
    (Please note that, in Socket.tcp, there are still cases where Errno::ETIMEDOUT
    may be raised in similar situations, and that in both cases Errno::ETIMEDOUT may be
    raised when the timeout occurs at the OS level.)

String

Thread

  • Introduce support for Thread#raise(cause:) argument similar to
    Kernel#raise. [Feature #21360]

Compatibility issues

  • ObjectSpace._id2ref is deprecated. [Feature #15408]

  • Process::Status#& and Process::Status#>> have been removed.
    They were deprecated in Ruby 3.3. [Bug #19868]

  • rb_path_check has been removed. This function was used for
    $SAFE path checking which was removed in Ruby 2.7,
    and was already deprecated.
    [Feature #20971]

  • A backtrace for ArgumentError of "wrong number of arguments" now
    include the receiver's class or module name (e.g., in Foo#bar
    instead of in bar). [Bug #21698]

  • Backtraces no longer display internal frames.
    These methods now appear as if it is in the Ruby source file,
    consistent with other C-implemented methods. [Bug #20968]

    Before:

    ruby -e '[1].fetch_values(42)'
    <internal:array>:211:in 'Array#fetch': index 42 outside of array bounds: -1...1 (IndexError)
            from <internal:array>:211:in 'block in Array#fetch_values'
            from <internal:array>:211:in 'Array#map!'
            from <internal:array>:211:in 'Array#fetch_values'
            from -e:1:in '<main>'
    

    After:

    $ ruby -e '[1].fetch_values(42)'
    -e:1:in 'Array#fetch_values': index 42 outside of array bounds: -1...1 (IndexError)
            from -e:1:in '<main>'
    

Stdlib compatibility issues

  • CGI library is removed from the default gems. Now we only provide cgi/escape for
    the following methods:

    • CGI.escape and CGI.unescape
    • CGI.escapeHTML and CGI.unescapeHTML
    • CGI.escapeURIComponent and CGI.unescapeURIComponent
    • CGI.escapeElement and CGI.unescapeElement

    [Feature #21258]

  • With the move of Set from stdlib to core class, set/sorted_set.rb has
    been removed, and SortedSet is no longer an autoloaded constant. Please
    install the sorted_set gem and require 'sorted_set' to use SortedSet.
    [Feature #21287]

C API updates

IO

  • rb_thread_fd_close is deprecated and now a no-op. If you need to expose
    file descriptors from C extensions to Ruby code, create an IO instance
    using RUBY_IO_MODE_EXTERNAL and use rb_io_close(io) to close it (this
    also interrupts and waits for all pending operations on the IO
    instance). Directly closing file descriptors does not interrupt pending
    operations, and may lead to undefined behaviour. In other words, if two
    IO objects share the same file descriptor, closing one does not affect
    the other. [Feature #18455]

GVL

  • rb_thread_call_with_gvl now works with or without the GVL.
    This allows gems to avoid checking ruby_thread_has_gvl_p.
    Please still be diligent about the GVL. [Feature #20750]

Set

  • A C API for Set has been added. The following methods are supported:
    [Feature #21459]

    • rb_set_foreach
    • rb_set_new
    • rb_set_new_capa
    • rb_set_lookup
    • rb_set_add
    • rb_set_clear
    • rb_set_delete
    • rb_set_size

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions