diff --git a/2.4.md b/2.4.md
index adb38b8..c05ad70 100644
--- a/2.4.md
+++ b/2.4.md
@@ -16,9 +16,9 @@ description: Ruby 2.4 full and annotated changelog
-->
* **Released at:** Dec 25, 2016 (NEWS file)
-* **Status (as of Jul 09, 2022):** EOL, latest is 2.4.10
+* **Status (as of Feb 04, 2023):** EOL, latest is 2.4.10
* **This document first published:** Oct 14, 2019
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
## Highlights[](#highlights)
diff --git a/2.5.md b/2.5.md
index fef18d1..90d2056 100644
--- a/2.5.md
+++ b/2.5.md
@@ -16,9 +16,9 @@ description: Ruby 2.5 full and annotated changelog
-->
* **Released at:** Dec 25, 2017 (NEWS file)
-* **Status (as of Jul 09, 2022):** EOL, latest is 2.5.9
+* **Status (as of Feb 04, 2023):** EOL, latest is 2.5.9
* **This document first published:** Jun 6, 2019
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
## Highlights[](#highlights)
@@ -145,7 +145,7 @@ When `uplevel: N` provided to `Kernel#warn`, the warning message includes the fi
Ruby 2.4 introduced redefinable `Warning.warn` method, which allowed to control how warnings are handled. But `warn` method wasn't calling it, so initially only warnings issued from C code could've been handled. 2.5 fixes it. (Was not mentioned in NEWS-2.5.0)
-* **Discussion:** [Feature #12299, comment #14-15](https://bugs.ruby-lang.org/issues/12299#note-14)
+* **Discussion:** Feature #12299#note-14
* **Documentation:** —
* **Code:**
```ruby
@@ -375,7 +375,9 @@ The underlying Onigm
ModernPerson.new('Meredith', 'Williams', 28)
# ArgumentError (wrong number of arguments (given 3, expected 0))
```
-* **Follow-up:** Ruby 3.1 introduced [a method](3.1.html#structclasskeyword_init) to check whether some struct should be initialized with keyword arguments, and [a warning](3.1.html#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) on erroneous initialization of non-keyword-args struct with a hash.
+* **Follow-ups:**
+ * Ruby 3.1 introduced [a method](3.1.html#structclasskeyword_init) to check whether some struct should be initialized with keyword arguments, and [a warning](3.1.html#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) on erroneous initialization of non-keyword-args struct with a hash.
+ * Ruby 3.2 [allowed](3.2.html#struct-can-be-initialized-by-keyword-arguments-by-default) all structs without explicit `keyword_init:` parameter specified to be initialized by both positional and keyword args.
### `Time.at`: units[](#timeat-units)
@@ -637,6 +639,11 @@ The constant `TMPFILE` is nil
+ ```
#### `File.lutime`[](#filelutime)
@@ -698,7 +705,7 @@ Much akin to `Hash#fetch`, allows to fetch thread-local variable or provide defa
### Misc[](#misc)
* `Random.raw_seed` renamed to Random.urandom. Discussion: Bug #9569.
-* `Data` is deprecated. It was a base class for C extensions, and it's not necessary to expose in Ruby level. Feature #3072. **Follow-up:** Fully removed in 3.0.
+* `Data` is deprecated. It was a base class for C extensions, and it's not necessary to expose in Ruby level. Feature #3072. **Follow-up:** Fully removed in 3.0; in 3.2, a new class with the same name but different meaning was [reintroduced](3.2.html#data-new-immutable-value-object-class).
## Standard library[](#standard-library)
diff --git a/2.6.md b/2.6.md
index de96c61..d21c075 100644
--- a/2.6.md
+++ b/2.6.md
@@ -8,9 +8,9 @@ description: Ruby 2.6 full and annotated changelog
# Ruby 2.6
* **Released at:** Dec 25, 2018 (NEWS file)
-* **Status (as of Jul 09, 2022):** EOL, latest is 2.6.10
+* **Status (as of Feb 04, 2023):** EOL, latest is 2.6.10
* **This document first published:** Dec 29, 2018
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important optimization introduced in 2.6, including the whole JIT big thing. That's not because they are not important, just because this site's goals are different.
@@ -472,6 +472,7 @@ Several enumerators can now be chained into one with `Enumerator#+(other)` or `E
# or redirected with `ruby test.rb 2> err.log
end
```
+* **Follow-up:** In Ruby 3.2, one more related method `#detailed_message` [was added](3.2.html#exceptiondetailed_message).
#### Exception output tweaking[](#exception-output-tweaking)
@@ -597,7 +598,10 @@ The new module provides an "official" Ruby parser, replacing stdlib Ripper (and
tree.children[2].children[0].children
# => [1]
```
-* **Notice:** One may be excited about `RubyVM::AbstractSyntaxTree.of(proc)`, but it doesn't mean the "real" extraction of code from live `Proc`, rather a simple hack with trying to find proc's source file and parse it.
+* **Note:** One may be excited about `RubyVM::AbstractSyntaxTree.of(proc)`, but it doesn't mean the "real" extraction of code from live `Proc`, rather a simple hack with trying to find proc's source file and parse it.
+* **Follow-up:** In Ruby 3.2, new options were added for `parse`, allowing to:
+ * Perform [fault-tolerant parsing](3.2.html#error_tolerant-true-option-for-parsing) (parse syntactically wrong/incomplete code);
+ * [Preserve tokens](3.2.html#keep_tokens-true-option-for-parsing) from source code alongside nodes;
### `TracePoint` improvements[](#tracepoint-improvements)
@@ -669,6 +673,9 @@ With new parameters provided, you can fine-tune for what methods or specific lin
```
* **Notice:** In absence of docs, we can at least point at [commit message](https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/66003):
> `code` should be consisted of `InstructionSequence` (iseq) (`RubyVM::InstructionSequence.of(code)` should not return nil). If code is a tree of iseq, `TracePoint` is enabled on all of iseqs in a tree.
+* **Follow-ups:**
+ * In 2.7, the docs have emerged, and a new parameter named `target_thread:` was introduced. It was missing from the official `NEWS`-file and therefore missing from this changelog _(which is a thing to be fixed!)_
+ * In 3.2, `target_thread:` began [defaulting to the current thread](3.2.html#tracepoint-for-block-default-to-trace-the-current-thread) with block form of `enable`.
## Standard library[](#standard-library)
diff --git a/2.7.md b/2.7.md
index 8b2a51f..8676b6b 100644
--- a/2.7.md
+++ b/2.7.md
@@ -8,9 +8,9 @@ description: Ruby 2.7 full and annotated changelog
# Ruby 2.7
* **Released at:** Dec 25, 2019 (NEWS file)
-* **Status (as of Jul 09, 2022):** 2.7.6 is current _stable_
+* **Status (as of Feb 04, 2023):** 2.7.7 is current _stable_
* **This document first published:** Dec 27, 2019
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
@@ -48,7 +48,10 @@ data in [{user: {login:}, title:, created_at:}, *] # match array of hashes, with
# => ["zverok", "Add pattern matching documentation", "2019-12-25T18:42:03Z"]
```
-* **Follow-up:** Pattern matching became a stable (non-experimental) feature, and its power expanded signficantly [in 3.0](#pattern-matching); and then it became even more flexible [in 3.1](3.1.html#pattern-matching).
+* **Follow-ups:**
+ * Pattern matching became a stable (non-experimental) feature, and its power expanded signficantly [in 3.0](#pattern-matching);
+ * Then, it became even more flexible [in 3.1](3.1.html#pattern-matching);
+ * In 3.2, several core and standard library classes (`MatchData`, `Time`, `Date`, `DateTime`) [became deconstructible](3.2.html#pattern-matching).
### Keyword argument-related changes[](#keyword-argument-related-changes)
diff --git a/3.0.md b/3.0.md
index 98ecf4b..1afb6f6 100644
--- a/3.0.md
+++ b/3.0.md
@@ -8,9 +8,9 @@ description: Ruby 3.0 full and annotated changelog
# Ruby 3.0
* **Released at:** Dec 25, 2020 (NEWS.md file)
-* **Status (as of Jul 09, 2022):** 3.0.4 is current _stable_
+* **Status (as of Feb 04, 2023):** 3.0.5 is current _stable_
* **This document first published:** Dec 25, 2020
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
## Highlights[](#highlights)
@@ -76,6 +76,7 @@ Just a leftover from the separation of keyword arguments.
# Ruby 3.0:
# args=[1, 2, {:a=>true}], kwargs={} -- no attempt to extract hash into keywords, and no error/warning
```
+* **Follow-up:** In Ruby 3.2, one more proc argument splatting behavior [was improved](3.2.html#keyword-argument-separation-leftovers).
### Arguments forwarding (`...`) supports leading arguments[](#arguments-forwarding--supports-leading-arguments)
@@ -126,6 +127,9 @@ Just a leftover from the separation of keyword arguments.
delegates(5)
```
+* **Follow-ups:**
+ * In Ruby 3.1, a separate anonymous block argument (bare `&`) forwarding [was added](3.1.html#anonymous-block-argument);
+ * In Ruby 3.2, separate positional and keyword (bare `*` and `**`) forwarding [were added](3.2.html#anonymous-arguments-passing-improvements).
### "Endless" method definition[](#endless-method-definition)
@@ -357,6 +361,7 @@ Pattern matching now supports "find patterns", with several splats in them.
```
* **Notes:**
* Feature is marked as EXPERIMENTAL, will warn so on an attempt of usage, and may change in the future.
+* **Follow-ups:** Feature considered not experimental since 3.2
### Changes in class variable behavior[](#changes-in-class-variable-behavior)
@@ -1030,6 +1035,7 @@ The method will return true for separate Proc instances if the procs were create
```
* **Notes:**
* See [Ractors](#ractors) explanation below, and Ractor class docs for deeper understanding of ractors data sharing model.
+* **Follow-up:** In 3.2, the constant was [removed](3.2.html#removals).
### Filesystem and IO[](#filesystem-and-io)
@@ -1127,7 +1133,9 @@ The improvement of inter-thread concurrency for IO-heavy tasks is achieved with
of blocking the whole thread, they were transferring control while waiting, and all three waits are
performed in parallel.
* **Notes:** The feature is somewhat unprecedented for Ruby in the fact that **no default Scheduler implementation** is provided. Implementing the Scheduler in a reliable way (probably using some additional non-blocking event loop library) is completely up to the user. Considering that the feature is implemented by [Samuel Williams](https://github.com/ioquatix) of the Async fame, the Async gem utilizes the new feature since the day of 3.0 release.
-* **Follow-up:** In Ruby 3.1, [more scheduler hooks were added](3.1.html#fiber-scheduler-new-hooks) to make more core methods support asynchronous execution.
+* **Follow-up:s**
+ * In Ruby 3.1, [more scheduler hooks were added](3.1.html#fiber-scheduler-new-hooks) to make more core methods support asynchronous execution;
+ * In 3.2, [even more](3.2.html#fiberschedulerio_select) hooks were added, and `SchedulerInteface` documentation abstraction was renamed to `Scheduler`.
#### `Thread.ignore_deadlock` accessor[](#threadignore_deadlock-accessor)
diff --git a/3.1.md b/3.1.md
index 3b6f019..ca1380c 100644
--- a/3.1.md
+++ b/3.1.md
@@ -1,16 +1,16 @@
---
title: Ruby 3.1 changes
-prev: /
-next: 3.0
+prev: 3.2
+next: 3.1
description: Ruby 3.1 full and annotated changelog
---
# Ruby 3.1
* **Released at:** Dec 25, 2021 (NEWS.md file)
-* **Status (as of Jul 09, 2022):** 3.1.2 is current _stable_
+* **Status (as of Feb 04, 2023):** 3.1.3 is current _stable_
* **This document first published:** Jan 5, 2022
-* **Last change to this document:** Jul 09, 2022
+* **Last change to this document:** Feb 04, 2023
> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important optimization introduced in 3.1, including a new JIT named YJIT. That's not because they are not important, just because this site's goals are different.
@@ -106,7 +106,7 @@ In hash literals and method calls, `x:` is now a shortcut for `x: x`—take hash
If method uses its block argument only to pass to another method, it can be marked by anonymous `&`.
-* **Reason:** The initial proposal for the feature is 6-year old and focused on avoiding intermediate blocks object allocation on block forwarding. It was considered redundant when block forwarding was optimized in Ruby 2.5; but then the core team decided it is actually a nice and unambiguous shortcut for methods that just pass the block further. As bloc argument is frequently called just `block`, the absence of the name doesn't affect readability.
+* **Reason:** The initial proposal for the feature is 6-year old and focused on avoiding intermediate blocks object allocation on block forwarding. It was considered redundant when block forwarding was optimized in Ruby 2.5; but then the core team decided it is actually a nice and unambiguous shortcut for methods that just pass the block further. As block argument is frequently called just `block`, the absence of the name doesn't affect readability.
* **Discussion:** Feature #11256
* **Documentation:** doc/syntax/methods.rdoc#Block Argument
* **Code:**
@@ -333,6 +333,7 @@ The class of the context (`self`) available inside the `refine SomeClass do ...
end
end
```
+* **Follow-ups:** In Ruby 3.2, several usability improvments using the new class [were introduced](3.2.html#refinements): ability to ask which refinements some module defines, which refinements are active in the current context, and what class or module the particular refinement refines.
#### `Module#prepend` behavior change[](#moduleprepend-behavior-change)
@@ -442,6 +443,7 @@ Now, when module is prepended to the class, it always becomes first in the ances
# => true
```
* **Notes:** Another idea discussed was just having `Method#visibility`, but it was hard to decide on the method's name and what it should return, while three predicates are more or less obvious.
+* **Follow-up:** [Reverted](3.2.html#methodpublic-protected-and-private-are-removed) in Ruby 3.2: it was considered after all that visibility is not a property of the method, but rather the responsibility of its owner.
### `Kernel#load`: module as a second argument[](#kernelload-module-as-a-second-argument)
@@ -729,6 +731,7 @@ The new parameter is accepting offsets or timezone objects, and (finally!) allow
#=> #"користувач", :application=>"застосунок"}>
# ...no braces necessary, no warning
```
+* **Follow-up:** Since 3.2, following the warning, the structs [can be initialized](3.2.html#struct-can-be-initialized-by-keyword-arguments-by-default) by both keyword and positional argument when `keyword_init: true` is not defined.
#### `StructClass#keyword_init?`[](#structclasskeyword_init)
@@ -895,8 +898,6 @@ A new class representing low-level I/O abstraction. Internally, uses OS mechanis
* **Discussion:** Feature #18020
* **Documentation:** IO::Buffer
* **Code:** _This is a big new class, see class' docs for detailed examples of usage, they are quite succinct._
-* **Notes:**
-* **Follow-up:**
### `File.dirname`: optional `level` to go up the directory tree[](#filedirname-optional-level-to-go-up-the-directory-tree)
diff --git a/3.2.md b/3.2.md
new file mode 100644
index 0000000..06065d8
--- /dev/null
+++ b/3.2.md
@@ -0,0 +1,1844 @@
+---
+title: Ruby 3.2 changes
+prev: /
+next: 3.1
+description: Ruby 3.2 full and annotated changelog
+---
+
+# Ruby 3.2
+
+* **Released at:** Dec 25, 2022 (NEWS.md file)
+* **Status (as of Feb 04, 2023):** 3.2.0 is current _stable_
+* **This document first published:** Feb 4, 2022
+* **Last change to this document:** Feb 04, 2023
+
+
+
+**🇺🇦 🇺🇦 Before you start reading the changelog: A full-scale Russian invasion into my home country continues. The only reason I am alive and able to work on the changelog is Armed Force of Ukraine, and international support with weaponry, funds and information. I am in my home city Kharkiv, preparing to join the army. Please care to read two of my appeals to Ruby community before proceeding: [first](https://zverok.space/blog/2022-03-03-WAR.html), [second](https://zverok.space/blog/2022-03-15-STILL-WAR.html).
We need all support we can get to push inviders out and to bring peace to our land. Please [spread information, lobby our cause and donate](https://war.ukraine.ua/).🇺🇦 🇺🇦**
+
+> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important optimization introduced in 3.2, including YJIT improvements and [object shapes](https://bugs.ruby-lang.org/issues/18776). That's not because they are not important, just because this site's goals are different.
+
+In preparation of this entry, [Cookpad's article](https://techlife.cookpad.com/entry/2022/12/26/121950) on notable changes in Ruby 3.2 written by core developers Koichi Sasada (ko1) and Yusuke Endoh (mame) provided invaluable insight. Thank you!
+
+## Highlights[](#highlights)
+
+* [Anonymous method argument passing](#anonymous-arguments-passing-improvements)
+* [More inspectable refinements](#refinements)
+* [`Data` class](#data-new-immutable-value-object-class)
+* [Support for pattern-matching in `Time` and `MatchData`](#pattern-matching)
+* [`Set` is a built-in class](#set-became-a-built-in-class)
+* [Per-`Fiber` storage](#fiber-storage)
+* [`RubyVM::AbstractSyntaxTree`: fault-tolerant and token-level parsing](#rubyvmabstractsyntaxtree)
+
+## Language changes[](#language-changes)
+
+### Anonymous arguments passing improvements[](#anonymous-arguments-passing-improvements)
+
+If the method declaration includes anonymous positional or keyword arguments (`*` or `**` without associated name), those arguments can now be passed to the next method with `some_method(*)` or `some_method(**)` syntax.
+
+* **Reason:** While it can be considered too cryptic shorcut by some, the new feature is consistent with passing of _all_ arguments with `...`.
+* **Discussion:** Feature #18351
+* **Documentation:** Methods: Array/Hash Argument
+* **Code:**
+ ```ruby
+ def only_keywords(**) # accept keyword arguments
+ p(**) # and pass them to the next method
+ end
+
+ def only_positional(*) # accept positional arguments
+ p(*) # and pass them to the next method
+ end
+
+ def both(*, **) # effectively the same as ...
+ p(*, **)
+ end
+
+ only_keywords(a: 1, b: 2)
+ # prints "{:a=>1, :b=>2}"
+ only_positional(1, 2, 3)
+ # prints
+ # 1
+ # 2
+ # 3
+ both(1, 2, 3, a: :b)
+ # prints
+ # 1
+ # 2
+ # 3
+ # {:a=>:b}
+
+ # Realistic usage: a small wrapper method, just "fall through" to the next one
+ def get(url, **) = send_request(:get, url, **)
+
+ # Named and unnamed could be freely mixed:
+ def mixed_naming(*a, **)
+ p a
+ p(**)
+ end
+
+ mixed_naming(1, 2, 3, a: :b)
+ # prints:
+ # [1, 2, 3]
+ # {:a=>:b}
+
+ # But using anonymous forwarding with named arguments is an error
+ def forward(*args) = p(*)
+ # no anonymous rest parameter (SyntaxError)
+
+ # Interestingly enough, not only calling methods, but also "repacking" values
+ # into variables work:
+ def repack(*, **)
+ x = * # this is syntax error
+ x = [*] # but this will work and put [1, 2] in x
+ x, y = [*] # and this will work and put 1 in x and 2 in y
+ z = {**} # this will put {a: :b} into z
+ end
+ repack(1, 2, a: :b)
+
+ # While the latter example might seem just a curiosity, it could help with
+ # quick debugging of path-through code.
+ # Imagine the `get` method above fails in one particular case.
+ # We can adjust it this way, temporarily:
+ def get(url, **)
+ binding.irb if {**}.dig(:headers, :content_type) == 'application/json'
+ send_request(:get, url, **)
+ end
+
+ # Parentheses are important for correct parsing.
+ # Imagine this:
+ def test(*)
+ # this, depending on further code, will be either a SyntaxError,
+ # or interpreted as call_something() * next_statement
+ call_something *
+ # ...
+ end
+
+ # Procs don't support anonymous arguments:
+ proc { |*| p(*) }
+ # Somewhat confusingly, this definitions raises:
+ # no anonymous rest parameter (SyntaxError)
+ # ...meaning surrounding method doesn't have them.
+
+ # And if it does, they would be used, not proc's arguments:
+ def test(*)
+ proc { |*| p(*) }.call(1)
+ end
+
+ test(2)
+ # prints 2 -- even inside proc, method's arguments are used for forwarding
+ ```
+* **Note:** Whether anonymous arguments should be supported in procs [is discussed](https://bugs.ruby-lang.org/issues/19370).
+
+### Constant assignment evaluation order changed[](#constant-assignment-evaluation-order-changed)
+
+For a long time, statements like `module_expression::CONST_NAME = value_expression` first evaluated `value_expression` and then `module_expression`. This was changed to calculate `module_expression` first.
+
+* **Reason:** Just making it consistent with other assignment expressions, which tend to calculate the left part before the right.
+* **Discussion:** Bug #15928
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ # synthetic demonstrational example:
+ def make_a_class
+ puts "Making a class"
+ Class.new
+ end
+
+ make_a_class::CONST = 42.tap { puts "Calculating the value" }
+ # Prints:
+ # In Ruby 3.1:
+ # Calculating the value
+ # Making a class
+ # In Ruby 3.2:
+ # Making a class
+ # Calculating the value
+
+ # Even simpler:
+ NonExistentModule::CONST = 42.tap { puts "Calculating the value" }
+ # Ruby 3.1:
+ # Prints "Calculating the value"
+ # raises "uninitialized constant NonExistentModule" (NameError)
+ # Ruby 3.2:
+ # just raises "uninitialized constant NonExistentModule" (NameError)
+ ```
+* **Note:** The problem is rarely relevant, but might eventually manifest itself in complicated metaprogramming or autoloading. Or, like in the last example, some effectful value might be calculated before discovering there is nowhere to put it in. According to discussion on the tracker, the old behavior was never intentional, it was just too hard to fix.
+
+### Behavior of module reopening/redefinition with included modules changed[](#behavior-of-module-reopeningredefinition-with-included-modules-changed)
+
+When some module/class name is available at the top level context from the included modules, and a new class/module is defined, previously it was considered a reopening of existing module; since Ruby 3.2, it is a creation of a new module.
+
+* **Reason:** As one file's code has no control what is included in other files (may be non-obvious to code's author), cryptic behaviors might've emerged by treating included modules as reopenable on a top level.
+* **Discussion:** Feature #18832
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ require 'net/http'
+
+ # ...might've happened in some of required files
+ include Net
+
+ p HTTP
+ #=> Net::HTTP -- from included Net module
+
+ # plan to define some of our app-specific HTTP services here
+ module HTTP
+ # ...
+ end
+ # Ruby 3.1: HTTP is not a module (TypeError)
+ # Because it assumes you reopening HTTP class from included Net
+ # The error is hard to understand and even harder to bypass
+ # Ruby 3.2: Successfully defines a new empty module, unrelated to Net::HTTP
+
+ p HTTP
+ #=> HTTP -- now it is a new module,
+ # and Net::HTTP is available only by fully-qualified name
+ ```
+
+### Keyword argument separation leftovers[](#keyword-argument-separation-leftovers)
+
+A few edge cases after [big keyword argument separation](3.0.html#keyword-arguments-are-now-fully-separated-from-positional-arguments) were fixed:
+
+* Erroneous autosplatting of positional arguments in procs. Discussion: Bug #18633
+ ```ruby
+ test = proc { |arg, **keywords| p(arg:, keywords:) }
+ test.call(1, 2)
+ # Prints: {:arg=>1, :keywords=>{}}, 2 is lost as an extra argument, as expected
+ # But...
+ test.call([1, 2])
+ # Ruby 3.1: prints {:arg=>1, :keywords=>{}}, extra unexpected splatting & loss of 2s
+ # Ruby 3.2: prints {:arg=>[1, 2], :keywords=>{}}, as expected
+ ```
+* The methods that splat arguments were fixed to consistently treat keyword arguments according to `ruby2_keywords` tag. Discussion: Bug #18625, Bug #16466
+ ```ruby
+ def method_with_keywords(**kw) = p kw
+
+ # This should never work: method is not marked with ruby2_keywords,
+ # so it shouldn't ever unpack positional arguments into keyword arguments.
+ def method_with_positional(*args) = method_with_keywords(*args)
+
+ method_with_positional(a: 1)
+ # Ruby 3.1 and 3.2: behaves as expected:
+ # wrong number of arguments (given 1, expected 0) (ArgumentError)
+
+ # This should work: method is marked with ruby2_keywords, so it
+ # is able to repack hash as keyword_args
+ ruby2_keywords def old_method(*args) = method_with_keywords(*args)
+
+ old_method(a: 1)
+ # Ruby 3.1 and 3.2: behaves as expected:
+ # prints {:a => 1}
+
+ # This shouldn't work, but erroneously worked in 3.1: after `bad_old_method`
+ # delegated the hash, it preserved "I am Ruby 2 keywords" through other
+ # methods (even if they aren't marked to be compatible).
+ ruby2_keywords def bad_old_method(*args) = method_with_positional(*args)
+
+ bad_old_method(a: 1)
+ # Ruby 3.1: prints {:a => 1}
+ # Ruby 3.2: wrong number of arguments (given 1, expected 0) (ArgumentError)
+ ```
+
+### Removals[](#removals)
+
+* Constants:
+ * `Fixnum` and `Bignum` (deprecated since unification into `Integer` in [2.4](/2.4.html#fixnum-and-bignum-are-unified-into-integer))
+ * `Random::DEFAULT` (deprecated in favor of per-Ractor random generator since [3.0](/3.0.html#randomdefault-behavior-change))
+* Methods:
+ * `Dir.exists?`, `File.exists?` (deprecated since 2.1 as a general rule of having "bare verb" as a method name)
+ * `Object#=~` (deprecated since [2.6](/2.6.html#minor-changes))
+ * `Object#taint`, `#untaint`, `#tainted?`, `#trust`, `#untrust`, `#untrusted?` (deprecated together with a general concept of "safety" since [2.7](/2.7.html#safe-and-taint-concepts-are-deprecated-in-general))
+
+
+## Core classes and modules[](#core-classes-and-modules)
+
+### `Kernel#binding` raises if accessed not from Ruby[](#kernelbinding-raises-if-accessed-not-from-ruby)
+
+`binding` is a method that returns "current context" (Binding) object, allowing access to local variables, `self`, evaluating code in that context, etc. The problem solved was that it was accessible from C methods, too, but as C methods call don't push new "execution frames," the binding returned was of the last calling Ruby method, which was useless and misleading. Since Ruby 3.2, the method raises an exceptions in such situations.
+
+* **Discussion:** Bug #18487
+* **Documentation:** Kernel#binding (no mention for the behavior in non-Ruby frame)
+* **Code:** To demonstrate the practical implications, the C code should be written, but to get the gist of what's happening, we can do this:
+ ```ruby
+ # The callable binding object, that will "bind" itself to argument, and call it in the context
+ binding_caller = Kernel.instance_method(:binding).method(:bind_call)
+
+ binding_caller.call(nil).local_variables
+ # [:binding_caller] -- it is performed in the current context
+
+ # method just accepts block and just calls it
+ def test1(&)
+ local_val = 'test'
+ yield(nil)
+ end
+
+ test1(&binding_caller).local_variables
+ #=> [:local_val] -- we got the binding of test1, as expected
+
+ def test2(&)
+ local_val = 'test'
+ # Expectations: we pass block further, so it will be performed
+ # inside #map, and the binding was to "insides" of each argument
+ [1].map(&)
+ end
+
+ # Reality: #map is method defined in C, it doesn't has its own
+ # "context frame",
+ test2(&binding_caller).first.local_variables
+ # So, in Ruby 3.1:
+ # => [:local_val] -- we still received binding of the previous method
+ # in call chain
+ # In Ruby 3.2:
+ # Cannot create Binding object for non-Ruby caller (RuntimeError)
+ ```
+* **Note:** `TracePoint#binding` was also adjusted for C methods, see [below](#tracepointbinding-returns-nil-for-c_callc_return).
+
+### Class and Module[](#class-and-module)
+
+#### `Class#attached_object`[](#classattached_object)
+
+For singleton classes, returns the object this class is for; otherwise, raises `TypeError`.
+
+* **Reason:** The "what is this singleton class around of" is useful in metaprogramming, introspection and code analysis, especially when some class methods are added via `class << self`.
+* **Discussion:** Feature #12084
+* **Documentation:** Class#attached_object
+* **Code:**
+ ```ruby
+ String.attached_object
+ # raises `String' is not a singleton class (TypeError)
+ "foo".singleton_class.attached_object
+ #=> "foo"
+
+ # or
+ class A
+ class << self
+ # here we are inside singleton class
+ p attached_object
+ #=> A
+ end
+ end
+
+ # Usage for advanced metaprogramming:
+
+ module MyCoolPlugin
+ def self.prepended(mod)
+ if mod.singleton_class? && mod.attached_object < Enumerable
+ mod.include MyCoolPlugin::ServicesForEnumerable
+ end
+ end
+ end
+
+ class Simple
+ class << self
+ # prepends only simple version of MyCoolPlugin
+ prepend MyCoolPlugin
+ end
+ end
+
+ class MyArray < Array
+ class << self
+ # Prepend MyCoolPlugin, and includes MyCoolPlugin::ServicesForEnumerable
+ prepend MyCoolPlugin
+ end
+ end
+
+ # Usage for documentation/code analysis purposes:
+
+ require 'active_support/all'
+ Time.zone #=> nil, defined by ActiveSupport
+
+ # Application-specific Time extensions
+ class MyTime < Time
+ end
+
+ m = MyTime.method(:zone)
+ # => #
+
+ # Now, if we want to path just this method to some
+ # documentation or introspection system, it has enough information
+ # to tell who it belongs to
+ m.owner
+ #=> #
+ m.receiver
+ #=> MyTime
+
+ # But before 3.2, there was no way to programmatically go from
+ # singleton class #, to the regular class Time, which
+ # the method is defined in, from the human point of view.
+
+ # Now, there is:
+ m.owner.attached_object
+ # => Time
+ # ...an our documentation/introspection system can properly describe
+ # it as a class method of Time.
+ ```
+* **Note:** The method will not work with "special" Ruby objects (`nil`, `true`, and `false`) which have their `singleton_class` implementations redefined to return regular class:
+ ```ruby
+ nil.singleton_class #=> NilClass, not #
+ class << nil
+ attached_object # raises `NilClass' is not a singleton class (TypeError)
+ end
+ ```
+
+#### `Module#const_added`[](#moduleconst_added)
+
+A "hook" method, called after the constant was defined in a module.
+
+* **Reason:** The method was proposed as helpful for autoloader libraries (like `zeitwerk`), but it also can be useful in metaprogramming, like "store a registry of nested classes of a particular type." Or "validate that some parent constant is redefined in a particular way."
+* **Discussion:** Feature #17881
+* **Documentation:** Module#const_added
+* **Code:**
+ ```ruby
+ module Test
+ def self.const_added(name)
+ puts "const_added: #{name} = #{const_get(name)}"
+ end
+
+ # The method is called AFTER the constant is actually
+ # defined, so its value is already available.
+ FOO = 1
+ # Prints:
+ # const_added: FOO = 1
+
+ # Each constant override triggers a method again:
+ FOO = 2
+ # Prints:
+ # const_added: FOO = 2
+
+ # Nested class definition:
+ class Nested
+ puts "Class definition body"
+ end
+ # Prints:
+ # const_added: Nested = Test::Nested
+ # Class definition body
+
+ # Note that const_added invoked at the BEGINNING of class being defined,
+ # not after its body is processed.
+ end
+ ```
+* **Note:** To understand the last example—why `const_added` was called before class definition is fully finished—you should consider that the class/module name becomes immediately known to Ruby after its definition is _opened_, allowing things like this:
+ ```ruby
+ module Test
+ class Nested
+ # outdated way of writing def self.class_method, but it works,
+ # because Nested is already known name here.
+ def Nested.class_method
+ end
+
+ # or this:
+ weird_class_method
+ # raises nice "undefined local variable or method `weird_class_method' for Test::Nested"
+ # ...because the name `Test::Nested` is already associated with current class
+ end
+ end
+ ```
+
+#### `Module#undefined_instance_methods`[](#moduleundefined_instance_methods)
+
+Lists methods that some module or class removed explicitly with `undef`.
+
+* **Reason:** _Honestly, I have no idea. The discussion ticket doesn't clarify this either! I assume it is just for completeness, to make everything that Module **can** do with method definitions to be accessible programmatically. Or, might help with debuggin of some evil/buggy code that does undefining of wrong methods. --zverok_
+* **Discussion:** Feature #12655
+* **Documentation:** Module#undefined_instance_methods
+* **Code:**
+ ```ruby
+ class ImmutableArray < Array
+ undef :select!, :reject! #, ... etc
+ end
+
+ class UserArray < ImmutableArray
+ undef :map
+ end
+
+ ImmutableArray.undefined_instance_methods #=> [:select!, :reject!]
+ UserArray.undefined_instance_methods #=> [:map] -- only methods undefined by this module, not ancestors
+ ```
+
+### Refinements[](#refinements)
+
+There are several new methods that improve the discovery and ability to debug for complicated code when it uses refinements. Those methods are improving answers to questions like "what methods are available in the current context and why?", "what methods will be available if I'll use that module" etc.
+
+#### `Module#refinements`[](#modulerefinements)
+
+Returns list of refinements the module defines.
+
+* **Discussion:** Feature #12737
+* **Documentation:** Module#refinements
+* **Code:**
+ ```ruby
+ module MathShortcuts
+ refine Numeric do
+ def sqrt = Math.sqrt(self)
+ # ...
+ end
+
+ refine String do
+ def calculate(binding) = "#{self} = #{eval(self, binding)}"
+ end
+ end
+
+ module NoRefinements
+ end
+
+ MathShortcuts.refinements #=> [#, #]
+ NoRefinemens.refinements #=> []
+
+ # Introspection: what would this refinement refine?..
+ # The method also added in 3.2, see below
+ MathShortcuts.refinements[0].refined_class #=> Numeric
+ # Introspection: what methods would it add?
+ # (false means "don't include methods defined in ancestors")
+ MathShortcuts.refinements[0].instance_methods(false) #=> [:sqrt]
+ ```
+
+#### `Refinement#refined_class`[](#refinementrefined_class)
+
+* **Discussion:** Feature #12737
+* **Documentation:** Refinement#refined_class
+* **Code:** See example above that demonstrates usage of `#refined_class` together with `Module#refinements`.
+* **Note:** The name of the method implies only classes can be refined, which is not true; modules can be refined also, and `refined_class` will promptly return them:
+ ```ruby
+ module BetterEnum
+ refine Enumerable do
+ def each2(&block) = each_slice(2, &block)
+ end
+ end
+
+ BetterEnum.refinements[0].refined_class #=> Enumerable
+
+ using BetterEnum
+ (1..6).each2.to_a #=> [[1, 2], [3, 4], [5, 6]]
+ ```
+ Currently [discussed](https://bugs.ruby-lang.org/issues/19366).
+
+#### `Module.used_refinements`[](#moduleused_refinements)
+
+Returns instances of `Refinement` used in the current context.
+
+* **Discussion:** Feature #14332
+* **Documentation:** Module.used_refinements
+* **Code:**
+ ```ruby
+ # See MathShortcuts module definition above
+
+ class Calculator
+ using MathShortcuts
+
+ # Works inside refined module...
+ p Module.used_refinements #=> [#, #]
+
+ def hypotenuse(c1, c2)
+ # ...and inside its methods
+ p Module.used_refinements #=> [#, #]
+
+ "(c1**2 + c2**2).sqrt".calculate(binding)
+ end
+ end
+
+ # Use method with refinements, triggering all the debug print
+ puts Calculator.new.hypotenuse(5, 6) #=> (c1**2 + c2**2).sqrt = 7.810249675906654
+
+ # Outside of refined class, refinements are empty
+ p Module.used_refinements #=> []
+ ```
+* **Notes:**
+ * Note that `used_refinements` is a _class_ method of a `Module`, and put there just for organizational purposes, while returning refinements list of the _current context_. There is no way to ask arbitrary module which refinements it uses (e.g., there is no `Calculator.used_refiments`).
+ * Just as a point of interest, the method with this name was proposed short after introducing of concept of refinements and was [discussed](https://bugs.ruby-lang.org/issues/7418) even before 2.0 release. It eventually became Module.used_modules introduced [in 2.4](2.4.html#moduleused_modules): that method just returned a list of modules with refinements, enabled in the current scope via `using`. The result of this method is not very fine-grained (as one refining method can refine _many_ objects at once, and it is impossible to inspect which exactly and what methods were added). After introduction of the `Refinement` class [in 3.1](3.1.html#refinement-class) it became reasonable to give easier access to particular refinements available in the current context.
+
+### Integer#ceildiv[](#integerceildiv)
+
+The integer division that always rounds up.
+
+* **Reason:** There are many simple use cases like pagination (when "21 items / 10 per page" should yield "3 pages"). It seems that the method is a direct equivalent of `a.fdiv(b).ceil`, and as such, annoyingly unnecessary, but `fdiv`, due to floating point imprecision, might produce surprising results in edge cases:
+ ```ruby
+ 99999999999999999.fdiv(1).ceil
+ # => 100000000000000000
+ 99999999999999999.ceildiv(1)
+ # => 99999999999999999
+ ```
+* **Discussion:** Feature #18809
+* **Documentation:** Integer#ceildiv
+* **Code:**
+ ```ruby
+ 9.ceildiv(3) #=> 3
+ 10.ceildiv(3) #=> 4
+ -10.ceildiv(3) #=> -3 -- always rounds up, regardless of the sign
+ # If the divisor is not integer, the result is equivalent to dividing by divisor.round
+ 10.ceildiv(2.1) #=> 5 -- like 10.ceildiv(2)
+ 10.ceildiv(2.6) #=> 4 -- like 10.ceildiv(3)
+ ```
+* **Note:** Unlike most of other operations, `#ceildiv` ignores numeric coercion protocols:
+ ```ruby
+ class StringNumber
+ def initialize(val) = @val = val.to_s
+ def coerce(other) = [other, @val.to_i]
+ end
+
+ 10 / StringNumber.new('3') #=> 3, argument is first converted with #coerce if possible
+ 10.fdiv StringNumber.new('3') #=> 3.3333333333333335, same
+ 10.ceildiv StringNumber.new('3') # ArgumentError
+ ```
+ It is already [fixed in the current master branch](https://github.com/ruby/ruby/pull/7118) and will behave as expected at Ruby 3.2.1
+
+### Strings and regexps[](#strings-and-regexps)
+
+#### Byte-oriented methods[](#byte-oriented-methods)
+
+Several method were added that operate on multibyte strings at byte-offset level, regardless of the encoding.
+
+* **Reason:** Low-level processing of strings (like networks middleware, or efficient search algorithms, or packing/unpacking) might need an ability to operate on a level of single bytes, regardless of original string's encoding. It is especially important while handling variable-length encodings like UTF-8. Before methods introduction, the only way to perform byte-level processing was to forcing string encoding into `ASCII-8BIT`, process, and then force encoding back.
+* **Discussion:** Feature #13110 (`String#byteindex`, `String#byterindex`, `MatchData#byteoffset`), Feature #18598 (`String#bytesplice`)
+* **Documentation:** String#byteindex, String#byterindex, String#bytesplice, MatchData#byteoffset
+* **Code:**
+ ```ruby
+ str = 'Слава Україні'
+
+ str.index('а') #=> 2, character index
+ str.byteindex('а') #=> 4, byte index, because Cyrilic letters in UTF-8 take 2 bytes each
+
+ str.rindex('а') #=> 9: character index of the last entrance of character 'а'
+ str.byterindex('а') #=> 17: byte index
+
+ match = str.match(/Слава\s+(?.+)/)
+ match.offset(1) #=> [6, 13]
+ match.byteoffset(1) #=> [11, 25]
+ match.offset(:name) #=> [6, 13]
+ match.byteoffset(:name) #=> [11, 25]
+
+ str = 'війна'
+ str.bytesplice(2..5, '...') #=> "..." -- returns replacement string
+ str #=> "в...на" -- original string's bytes 2-3, 4-5 (e.g. chars 1 and 2) are replaced
+
+ # Unlike byteslice getter, bytesplice setter checks character boundaries:
+ str = 'війна'
+ str.byteslice(1..3) #=> "\xB2і" -- works, even if the slice is mid-character
+ str.bytesplice(1..3, '...') # offset 1 does not land on character boundary (IndexError)
+ ```
+* **Note:** After 3.2 release, `bytesplice` behavior [had changed](https://bugs.ruby-lang.org/issues/19314#note-8) to return `self` instead of replacement string.
+
+#### `String#dedup` as an alias for `-"string"`[](#stringdedup-as-an-alias-for--string)
+
+The method produces frozen and deduplicated string without changing the receiver.
+
+* **Reason:** Since [Ruby 2.5](2.5.html#string--optimized-for-memory-preserving), `-"string"` produces a frozen and **deduplicated** copy: all instances with the same content take the same place in memory. But it is a less-known fact, that is also hard to guess from the code and quick look into the docs. At the same time, it became a useful idiom for reducing a memory footprint of long-running applications. The `#dedup` alias is focused on the behavior, and also more chainable than unary `-`.
+* **Discussion:** Feature #18595
+* **Documentation:** String#dedup
+* **Code:**
+ ```ruby
+ protocols = %w[http https]
+ domains = %w[company.com api.company.com]
+
+ # if in various places of the program we constructing the same URLs many times...
+ # ...there might be many similar strings sitting everywhere and taking memory
+ urls = 100.times.map { protocols.sample + '://' + domains.sample }
+ urls.uniq.count
+ # => 4 -- we store 4 same strings again and again
+ urls.map(&:object_id).uniq.count
+ # => 100 -- but it is 100 different objects
+
+ urls.map!(&:dedup)
+ urls.map(&:object_id).uniq.count
+ # => 4
+
+ # The `map(&:dedup)` above could previously been written as
+ urls.map!(&:-@)
+ # ...which calls unary minus on arguments
+ # But it is both uglier, and shows the intention worse.
+ ```
+
+#### `Regexp.new`: passing flags as a string is supported[](#regexpnew-passing-flags-as-a-string-is-supported)
+
+* **Reason:** most of those working with regexps are used to short flag names like `/foo/i` or `/bar/m`. At the same time, when `Regexp.new` is constructed dynamically, there was necessary to use numeric flags `Regexp::IGNORECASE | Regexp::MULTILINE`. They are are more formal (and can be thought as more obvious), but string ones are those most of the Rubyists remember.
+* **Discussion:** Feature #18788
+* **Documentation:** Regexp.new (`options` argument)
+* **Code:**
+ ```ruby
+ Regexp.new('username', 'i') #=> /username/i
+ # All known options work:
+ Regexp.new(<<~'HTML', 'imx')
+ <(\w+) .*?>
+ [^<]+
+ \1>
+ HTML
+ #=> same as
+ # %r{<(\w+) .*?>
+ # [^<]+
+ # \1>}imx
+
+ # Unknown option raises
+ Regexp.new('foo', 'g') # unknown regexp option: g (ArgumentError)
+ ```
+* **Notes:** One quirk that might be surprising with a wrong use of the new feature is that `Regexp.new` treats any truthy value of unrecognized type as "ignore case". So,
+ ```ruby
+ # This might erroneously thought to "work":
+ Regexp.new('foo', %w[i]) #=> /foo/i, looks like array of options is also acceptable?..
+ # ...but actually it is that any truthy value is treated as "ignorecase = true":
+ Regexp.new('foo', %w[abc]) #=> /foo/i
+ ```
+
+#### `Regexp`: ReDoS vulnerability prevention[](#regexp-redos-vulnerability-prevention)
+
+* **Reason:** The [ReDoS attack](https://en.wikipedia.org/wiki/ReDoS) is overloading the system by providing malformed regexp or string to match. The possibility for this attack is mostly theoretical, but still reported as a security vulnerability in some contexts. New Ruby version introduces several features that might mitigate the attack (or, at least, a vulnerability report):
+ * Setting explicit timeout for Regexp execution;
+ * `Regexp.linear_time?` analysis method;
+ * _(CRuby-specific) Cache-based optimization: many Regexps now perform in linear time even on very long strings (at the cost of increased memory consumption);_
+* **Discussion:** Feature #17837 (timeout), Feature #19104 (cache-based optimization), Feature #19194 (`.linear_time?`)
+* **Documentation:** Regexp.timeout, Regexp.timeout=, Regexp.new (`timeout:` keyword argument), Regexp.linear_time?
+* **Code:**
+ ```ruby
+ Regexp.linear_time?(/a+$/) #=> true
+ Regexp.linear_time?(/(a+)\1*$/) #=> false, backtracking is complicated
+
+ Regexp.timeout = 0.005
+ # Just a demo: simple yet very ambigous regexp applied to very large string
+ /(a+)\1*$/.match?('a' * 1_000_000)
+ # Depending on your machine's performance, might raise:
+ # `match?': regexp match timeout (Regexp::TimeoutError)
+
+ # When applied to a smaller string
+ /(a+)\1*$/.match?('a' * 1_000)
+ #=> true
+
+ # This works, too:
+ Regexp.new(/(a+)*$/, timeout: 0.005).match?('a' * 1_000_000)
+ # Might raise:
+ # `match?': regexp match timeout (Regexp::TimeoutError)
+ ```
+* **Note:** While `Regexp.linear_time?` is part of the official language API, its results for the same regexps might change between versions and implementations.
+
+### `Time.new` can parse a string[](#timenew-can-parse-a-string)
+
+The new protocol for `Time.new` is introduced, that parses Time from string.
+
+* **Reason:** Before Ruby 3.2, there core class `Time` provided no way to to get back a `Time` value from any serialization, including even simple `Time#inspect` or `#to_s`. The `Time.parse` provided by standard library `time` (not core functionality, doesn't work with explicit `require 'time'`), and tries to parse every imaginable format, while `Time.new` with string is stricter.
+* **Discussion:** Feature #18033
+* **Documentation:** Time.new
+* **Code:**
+ ```ruby
+ Time.new('2023-01-29 00:29:30')
+ # => 2023-01-29 00:29:30 +0200
+
+ # Desired timezone can be provided as part of a string:
+ Time.new('2023-01-29 00:29:30 +08:00')
+ #=> 2023-01-29 00:29:30 +0800
+ # ...or like with other .new protocols, as a separate in: argument:
+ Time.new('2023-01-29 00:29:30', in: '+08:00')
+ #=> 2023-01-29 00:29:30 +0800
+
+ # The accepted format is much stricter than Time.parse:
+ require 'time'
+ Time.parse('Jan 29, 2023')
+ #=> 2023-01-29 00:00:00 +0200
+ Time.new('Jan 29, 2023')
+ # in `initialize': can't parse: "Jan 29, 2023" (ArgumentError)
+
+ # Even incomplete time is considered an error (but see Notes below):
+ Time.new('2023-01-29 00:29')
+ # in `initialize': missing sec part: 00:29 (ArgumentError)
+ ```
+* **Notes:**
+ * A few improvements are planned to be made to the parser strictness and robustness in 3.2.1 (see Bug #19296, Bug #19293), for example:
+ ```ruby
+ # This works, but is considered a bug, the method should allow
+ # only fully-specified time
+ Time.new("2023-01-29")
+ #=> 2023-01-29 00:00:00 +0200
+ ```
+ * `Time.new('2023')` works, too, but it is a feature that worked before (force-conversion of singular year argument to integer), see Bug #19293. It will probably be deprecated, but can't be quickly removed due to backward compatibility.
+
+### `Struct` and `Data`[](#struct-and-data)
+
+#### `Struct` can be initialized by keyword arguments by default[](#struct-can-be-initialized-by-keyword-arguments-by-default)
+
+The default behavior of `Struct` since 3.2 is to accept both positional and keyword arguments in constructor.
+
+* **Reason:** Since introduction of `Struct.new(, keyword_init: true)` in 2.5, it was frequently criticized as clumsy
+* **Discussion:** Feature #16806
+* **Documentation:** Struct.new
+* **Code:**
+ ```ruby
+ User = Struct.new(:id, :name)
+ # This works:
+ User.new(1, 'Joan') #=> #
+ # Since 3.2, this works too:
+ User.new(id: 1, name: 'Joan') #=> #
+
+ # keyword_arguments: true/false still can be provided to make the behavior stricter:
+
+ User = Struct.new(:id, :name, keyword_init: true)
+ User.new(id: 1, name: 'Joan') #=> #
+ User.new(1, 'Joan')
+ # in `initialize': wrong number of arguments (given 2, expected 0) (ArgumentError)
+
+ User = Struct.new(:id, :name, keyword_init: false)
+ User.new(1, 'Joan') #=> #
+ User.new(id: 1, name: 'Joan')
+ # => #1, :name=>"Joan"}, name=nil>
+ # Note it is not ArgumentError, but interpreting all keyword args as one positional hash
+ ```
+* **Notes:**
+ * The incompatibility might be introduced by code that expected singular hash as an argument for a Struct initialization:
+ ```ruby
+ Wrapper = Struct.new(:json_data)
+ Wrapper.new(user: {name: 'Joan'})
+ # Ruby 3.0: works
+ # #{:name=>"Joan"}}>
+ # Ruby 3.1: warns, yet works
+ # warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).
+ # #{:name=>"Joan"}}>
+ # Ruby 3.2: breaks
+ # in `initialize': unknown keywords: user (ArgumentError)
+
+ # Fixed by explicitly setting `keyword_init: false` in struct definition:
+ Wrapper = Struct.new(:json_data, keyword_init: false)
+ Wrapper.new(user: {name: 'Joan'})
+ # => #{:name=>"Joan"}}>
+ # ...on Ruby 2.5-3.2, without any warnings
+
+ # or, alternatively, as always wrapping hashes in {} explicitly,
+ # as 3.1's warning suggested:
+ Wrapper.new({user: {name: 'Joan'}})
+ # => #{:name=>"Joan"}}>
+ ```
+ * While the new behavior is convenient, one should be especially careful when redefining `#initialize` for `Struct`s to not break it:
+ ```ruby
+ User = Struct.new(:id, :new) do
+ # suppose we want to convert id to Integer before initializing.
+ # Note that it could be in `args.first`, or in `kwargs[:id]` now, so it is either this:
+ def initialize(*args, **kwargs)
+ if !args.empty?
+ args[0] = args[0].to_i
+ elsif kwargs.key?(:id)
+ kwargs[:id] = kwargs[:id].to_i
+ end
+ super(*args, **kwargs)
+ end
+
+ # or just post-processing...
+ def initialize(...)
+ super(...)
+ self.id = self.id.to_i
+ end
+ end
+ ```
+
+#### `Data`: new immutable value object class[](#data-new-immutable-value-object-class)
+
+A new class for containing value objects: it is somewhat similar to `Struct` (and reuses some of the implementation internally), but is intended to be immutable, and have more modern and cleaner API.
+
+* **Reason:** Before 3.2, `Struct` was an ubiquitous data holder class in Ruby, but being designed a long time ago, it has its drawbacks, making it not suitable for all situations: it is mutable by design (have argument setters), and have APIs of both "value-alike" and "container-alike" types. But there is a lot of code using `Struct` in various ways (and for good reasons), so it can't be just redesigned. Several approaches was considered (including adding a "configure" API to `Struct`, allowing to specify "should it be mutable, should it be iterable, should it be hash-alike"), but in the end, a new class with smaller and stricter API was designed.
+* **Discussion:** Feature #16122
+* **Documentation:** Data
+* **Code:** Data is completely new, well-documented class. So we wouldn't try to demonstrate all details of its behavior, just give a brief overview.
+ ```ruby
+ Point = Data.define(:x, :y)
+
+ # Both positional and keyword arguments can be used
+ p1 = Point.new(1, 0) #=> #
+ p2 = Point.new(x: 0, y: 1) #=> #
+
+ # all arguments are mandatory
+ Point.new(1) # missing keyword: :y (ArgumentError)
+
+ # #initialize might be redefined to provide default arguments or argument conversions
+ Point3D = Data.define(:x, :y, :z) do
+ def initialize(x:, y:, z: 0) = super
+ end
+
+ Point3D.new(x: 1, y: 2)
+ # => #
+
+ # the redefinition above is enough to handle keyword AND position arguments:
+ Point3D.new(1, 2)
+ # => #
+
+ # there is no setters or any other way to change already created object
+ p1.x = 5 # undefined method `x=' for # (NoMethodError)
+ p1.instance_variable_set('@z', 100) # can't modify frozen Point: # (FrozenError)
+
+ # #with method can be used to construct new instances,
+ # replacing only parts of the data:
+ p1.with(y: 100) #=> #
+ ```
+* **Notes:**
+ * The class with the same name (`Data`) existed before for internal purposes—as a recommended empty base class for classes defined in C extensions. It was deprecated [since Ruby 2.5](2.5.html#misc), and removed in Ruby 3.0.
+ * On `Data` immutability: note that only the `Data`-derived object itself is frozen, but there is no deep freezing of instance variables. So this is still possible (and up to user code to prevent, if undesirable):
+ ```ruby
+ Result = Data.define(:array)
+ res = Result.new([1, 2, 3])
+ res.instance_variable_set('@size', 3) #=> can't modify frozen Result, as expected
+ # but...
+ res.array << 4 # works
+ res
+ #=> #
+
+ # Can shoot yourself in the foot in code doing something like...
+ case res
+ in Result(array:) # unpack into local variable
+ array.reverse! # process it inplace, considering it independent local variable...
+ # ...pass processed somewhere else...
+ end
+
+ # ...but actually data WAS changed:
+ res
+ #=> #
+ ```
+ * `#with` method in Ruby 3.2 is naive and just copies all old and new attributes to the new instance, without invoking any custom initialization methods. In the next version, though, it [expected to call `#initialize`](https://bugs.ruby-lang.org/issues/19259):
+ ```ruby
+ Point = Data.define(:x, :y) do
+ def initialize(x:, y:) = super(x: x.to_i, y: y.to_i)
+ end
+
+ p = Point.new('1', '2')
+ # => # -- conversion performed through #initialize
+ p.with(y: '3')
+ # => # -- #initialize is bypassed
+
+ # Probably since Ruby 3.2.1:
+ p.with(y: '3')
+ # => #
+ ```
+
+### Pattern matching[](#pattern-matching)
+
+* "Find pattern" `value in [*, pattern, *]` is no longer experimental. Feature #18585
+
+#### `MatchData`: added `#deconstruct` and `#deconstruct_keys`[](#matchdata-added-deconstruct-and-deconstruct_keys)
+
+As a part of the effort to make core classes more pattern matching friendly, `MatchData` (the result of regexp matching) now can be deconstructed.
+
+* **Discussion:** Feature #18821
+* **Documentation:** MatchData#deconstruct, MatchData#deconstruct_keys
+* **Code:**
+ ```ruby
+ case connection_string.match(%r{postgres://(\w+):(\w+)@(.+)})
+ in 'admin', password, server
+ # do connection with admin rights
+ in ^DEV_USERS, _, 'dev-server.local'
+ # connect to dev server with any password
+ in user, password, server
+ # do regular connection
+ end
+
+ # Might be used just for quick and expressive unpacking of match results
+ connection_string = 'postgres://admin:secret@foo.amazonaws.com'
+ connection_string.match(%r{postgres://(\w+):(\w+)@(.+)}) => user, password, server
+ user #=> "admin"
+ password #=> "secret"
+ server #=> "foo.amazonaws.com"
+
+ # When named capture group is used, MatchData also provides hash unpacking:
+ connection_string.match(%r{postgres://(?\w+):(?\w+)@(?.+)}) => user:, password:, server:
+ user #=> "admin"
+ password #=> "secret"
+ server #=> "foo.amazonaws.com"
+```
+
+#### `Time#deconstruct_keys`[](#timedeconstruct_keys)
+
+`Time` now can be used in pattern matching too.
+
+* **Discussion:** Feature #19071
+* **Documentation:** Time#deconstruct_keys
+* **Code:**
+ ```ruby
+ # `deconstruct_keys(nil)` shows all available keys:
+ Time.now.deconstruct_keys(nil)
+ # => {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0, :hour=>17, :min=>5, :sec=>56, :subsec=>(148452241/200000000), :dst=>false, :zone=>"EET"}
+
+ # Usage in pattern-matching:
+ case timestamp
+ in year: ...2022
+ puts "Far past!"
+ in year: 2022, month: 1..3
+ puts "Last year's first quarter"
+ in year: 2023, month:, day:
+ puts "#{day} of #{month}th month!"
+ # ...
+ end
+
+ # Check if it is the first Thursday of the current month:
+ if Time.now in wday: 4, day: ..7
+ # ...
+ ```
+
+* **Notes:**
+ * It was decided that `#deconstruct` method for `Time` doesn't make much sense, because the reasonable order for **all** of the time components is hard to define.
+ * Standard library classes `Date` and `DateTime` also receive similar implementations (Date#deconstruct_keys, DateTime#deconstruct_keys):
+ ```ruby
+ require 'date'
+
+ Date.today.deconstruct_keys(nil)
+ #=> {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0}
+ DateTime.now.deconstruct_keys(nil)
+ # => {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0, :hour=>17, :min=>19, :sec=>15, :sec_fraction=>(478525469/500000000), :zone=>"+02:00"}
+ ```
+
+### Enumerables and collections[](#enumerables-and-collections)
+
+#### `Enumerator.product`[](#enumeratorproduct)
+
+Generates an enumerator from several other, yielding all possible combinations of their elements.
+
+* **Discussion:** Feature #18685
+* **Documentation:** Enumerator.product, Enumerator::Product
+* **Code:**
+ ```ruby
+ enumerator = Enumerator.product(1.., %w[test me])
+ # => #
+
+ enumerator.take(6)
+ # => [[1, "test"], [1, "me"], [2, "test"], [2, "me"], [3, "test"], [3, "me"]]
+
+ # The arguments can be any object responding to `each_entry`,
+ # not necessary enumerator/enumerable
+ class ThreeBears
+ def each_entry
+ yield 'Papa Bear'
+ yield 'Mama Bear'
+ yield 'Little Bear'
+ end
+ end
+ Enumerator.product([1, 2], ThreeBears.new).to_a
+ # => [[1, "Papa Bear"], [1, "Mama Bear"], [1, "Little Bear"],
+ # [2, "Papa Bear"], [2, "Mama Bear"], [2, "Little Bear"]]
+ ```
+* **Notes:**
+ * It is [currently discussed](https://bugs.ruby-lang.org/issues/19324) that protocol for `Enumerator.product` is unlike `Array#product` (which is a method of the first argument of the expression).
+ * If one of the enumerators is effectful (can be iterated through only once), the current implementation would exhaust it on the first go:
+ ```ruby
+ require 'stringio'
+
+ # This will work as expected
+ io = StringIO.new('abc')
+ Enumerator.product(io.each_char, [1, 2, 3]).to_a
+ # => [["a", 1], ["a", 2], ["a", 3], ["b", 1], ["b", 2], ["b", 3], ["c", 1], ["c", 2], ["c", 3]]
+
+ # But this will produce less data than the full cross-product
+ # This will work as expected
+ io = StringIO.new('abc')
+ Enumerator.product([1, 2, 3], io.each_char).to_a
+ #=> [[1, "a"], [1, "b"], [1, "c"]]
+ ```
+ This is probably a [bug](https://bugs.ruby-lang.org/issues/19294).
+
+#### `Hash#shift` always returns `nil` if the hash is empty[](#hashshift-always-returns-nil-if-the-hash-is-empty)
+
+There was a bug/inconsistency with returning the default value if it is defined.
+
+* **Discussion:** Bug #16908
+* **Documentation:** Hash#shift
+* **Code:**
+ ```ruby
+ h = {a: 1}
+ h.shift #=> [:a, 1]
+ h.shift #=> nil, as expected
+ # but if the default for hash is defined...
+ h.default = :foo
+ h.shift
+ # 3.1: => :foo -- hard to explain, it isn't even [key, value] pair
+ # 3.2: => nil
+ ```
+
+#### `Set` became a built-in class[](#set-became-a-built-in-class)
+
+Previously a part of standard library, Set (a collection of unique elements) was promoted to core class. No more need to `require 'set'` to use the class.
+
+* **Discussion:** Feature #16989
+* **Documentation:** Set (still mentions `require 'set'`, though)
+* **Notes:** As of 3.2, the only change is making the library auto-required without changing the implementation. `Set` is still not as integrated in Ruby as other collections, like `Hash` and `Array`: `Set` is implemented in Ruby, uses `Hash` as its internal storage (by creating a hash of `set_element => true` pairs), and doesn't have its own literal. There are distant plans to improve it, but with no particular schedule.
+
+#### `Thread::Queue`: timeouts for `pop` and `push`[](#threadqueue-timeouts-for-pop-and-push)
+
+`timeout: ` parameter was added to methods `Queue#pop`, `SizedQueue#push`, `SizedQueue#pop`.
+
+* **Reason:** As thread queue is meant as a method of inter-thread communication, it is useful to provide a way for not hung a thread forever while waiting for input from other thread (or waiting for place in queue in case of `SizedQueue#push`)
+* **Discussion:** Feature #18774, Feature #18944
+* **Documentation:** Thread::Queue#pop, Thread::SizedQueue#pop, Thread::SizedQueue#push
+* **Code:**
+ ```ruby
+ queue = Thread::Queue.new
+ sender = Thread.new do
+ queue.push(1)
+ queue.push(2)
+ end
+
+ # Expects 3 values from sender
+ receiver = Thread.new do
+ # This will print 1, 2, and then make receiver sleep forever:
+ # 3.times.each { p queue.pop }
+
+ # But this prints 1, 2, waits for 0.5 seconds and then prints `nil`
+ 3.times.map { p queue.pop(timeout: 0.5) }
+ end
+ [sender, receiver].each(&:join)
+
+ sized = Thread::SizedQueue.new(2)
+ sized.push(1, timeout: 0.5) #=> success, returns the queue object
+ sized.push(2, timeout: 0.5) #=> success, returns the queue object
+ sized.push(3, timeout: 0.5) #=> waits 0.5 seconds, returns nil
+ sized.size #=> 2, only 1 and 2 were pushed successfully
+ ```
+
+### Procs and methods[](#procs-and-methods)
+
+#### `Proc#dup` returns an instance of subclass[](#procdup-returns-an-instance-of-subclass)
+
+* **Reason:** Just for consistency with other core classes behavior.
+* **Discussion:** Bug #17545
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ class MyProc < Proc
+ # some additional custom methods...
+ end
+
+ MyProc.new { }.dup
+ # 3.1: => #
+ # 3.2: => #
+ ```
+* **Notes:**
+ * In general, inheriting from core classes is a questionable practice, and you probably should avoid it;
+ * Despite producing an instance of a subclass now, `#dup` doesn't call `#initialize_dup` constructor, so no custom data that you've associated with a subclass instance can't be preserved:
+ ```ruby
+ class TaggedProc < Proc
+ attr_reader :tag
+
+ def initialize(tag, &block)
+ @tag = tag
+ super(&block)
+ end
+
+ def initialize_dup(other) # this will NOT be invoked
+ @tag = other.tag
+ super
+ end
+ end
+
+ t = TaggedProc.new('test') { }
+ t.tag #=> 'test'
+ t.dup.tag #=> nil
+ ```
+ This is a [bug](https://bugs.ruby-lang.org/issues/19362) and will probably change in Ruby 3.2.1.
+
+#### `Proc#parameters`: new keyword argument `lambda: true/false`[](#procparameters-new-keyword-argument-lambda-truefalse)
+
+`parameters(lambda: true)` returns Proc parameters description _as if the proc was lambda_ (e.g. the parameters without defaults was mandatory), regardless of Proc's real "lambdiness."
+
+* **Reason:** The regular (non-lamba) proc always reports its positional arguments is optional. It corresponds to its behavior, but loses the information which of them have default values defined. It might be inconvenient when using procs in metaprogramming, like building wrapper objects, or defining methods based on procs.
+* **Discussion:** Feature #15357
+* **Documentation:** Proc#parameters
+* **Code:**
+ ```ruby
+ prc = proc { |x, y=0| p(x:, y:) }
+ prc.parameters
+ # => [[:opt, :x], [:opt, :y]] -- for proc, all parameters are optional
+ # Whih corresponds to how it actually behaves: all params can be skipped:
+ prc.call
+ #=> {:x=>nil, :y=>0}
+
+ prc.parameters(lambda: true)
+ # => [[:req, :x], [:opt, :y]] -- in stricter lambda protocol, first parameter is required
+ # Which corresponds to how the corresponding lambda would treat
+ # its parameters:
+ lambda { |x, y=0| p(x:, y:) }.call
+ # wrong number of arguments (given 0, expected 1..2) (ArgumentError)
+
+ # The `lambda: false` call works, too, although arguably less useful:
+ l = ->(x, y=0) { }
+ l.parameters
+ # => [[:req, :x], [:opt, :y]]
+ l.parameters(lambda: false)
+ # => [[:opt, :x], [:opt, :y]]
+ ```
+
+#### `Method#public?`, `#protected?`, and `#private?` are removed[](#methodpublic-protected-and-private-are-removed)
+
+Predicates to check method visibility added in Ruby 3.1 were reverted.
+
+* **Reason:** The new feature implementation have led to several bugs with `Method` class behavior; while investigating the root cause for those bugs, Matz have decided that method's visibility is not its inherent property, but rather a property of the module/object that owns the method, and as such, is already present in form of `Module#{private,public,protected}_instance_methods` and `Object#{private,public,protected}_methods`
+* **Discussion:** Feature #11689#note-24
+* **Notes:** The discussion whether the feature should be un-reverted is still ongoing!
+
+#### `UnboundMethod`: more consistent reporting on what module it belongs to[](#unboundmethod-more-consistent-reporting-on-what-module-it-belongs-to)
+
+Since 3.2, `UnboundMethod`'s `#inspect` and comparison with other `UnboundMethod` only considers the module it defined in, not the actual module it was unbound from.
+
+* **Reason:** The change just aligns auxiliary methods with the main `UnboundMethod` implementation. No usage of unbound method is affected by what was the original class or object it was unbound from, only by the place of definition.
+* **Discussion:** Feature #18798
+* **Affected methods:** UnboundMethod#==, `#inspect` (documentation not updated)
+* **Code:**
+ ```ruby
+ tally = Array.instance_method(:tally)
+ p tally
+ # 3.1: => #
+ # 3.2: => #
+ # The former reports "it was defined in Enumerable, but unbound from Array"
+
+ orig_tally = Enumerable.instance_method(:tally)
+ tally == orig_tally
+ # 3.1: false -- because it was unbound from different class
+ # 3.2: true
+
+ # In reality, both are the same, and can be rebound to any class including Enumerable:
+ orig_tally.bind("test".each_char).call
+ # => {"t"=>2, "e"=>1, "s"=>1}
+ tally.bind("test".each_char).call
+ # => {"t"=>2, "e"=>1, "s"=>1} -- on 3.1, this worked, even if tally was "unbound from Array"
+
+ # Therefore, reporting `tally` as belonging to Array and unequal to `orig_tally` was misleading
+ ```
+* **Note:** While it might seem like a weird unnecessary quirk, unbinding methods and then rebinding them to different objects is useful metaprogramming technique when redefining some core methods to preserve and reuse the initial implementation.
+
+### IO and network[](#io-and-network)
+
+#### `IO`: support for timeouts for blocking IO[](#io-support-for-timeouts-for-blocking-io)
+
+`IO#timeout` getter and setter were added to the base class, and are respected on blocking operations.
+
+* **Discussion:** Feature #18630
+* **Documentation:** IO#timeout, IO#timeout=
+* **Code:**
+ ```ruby
+ STDIN.timeout = 5
+ print "Tell me what: "
+ answer = gets
+ # If you didn't print anything for 5 seconds, this raises:
+ # in `gets': Blocking operation timed out! (IO::TimeoutError)
+
+ STDIN.timeout = nil # to remove the timeout
+ answer = gets
+ # will wait till input appears or process will be killed
+
+ STDIN.timeout = 0
+ answer = gets
+ # Will raise IO::TimeoutError immediately,
+ # useful for quick "take something from input buffer if it isn't empty"
+ ```
+* **Note:** `IO#timeout` in general affects reading and writing operations (including network ones, defined on Socket). Operations like `IO.open` and `IO#close` are **not** affected.
+
+#### `IO#path`[](#iopath)
+
+Any `IO` object can be constructed with additional argument `path:`, which will be available as a `path` attribute.
+
+* **Reason:** `IO` object could be created from low-level file descriptor (for example, returned by some C extension), but there was no way to specify it corresponds to some specific filesystem path.
+* **Discussion:** Feature #19036
+* **Documentation:** IO#Open options, IO#path
+* **Code:**
+ ```ruby
+ # Always worked:
+ f = File.open('README.md')
+ f.path #=> 'README.md'
+
+ # IO created from system-level file descriptor (which might've been returned by a C library)
+ io = IO.new(f.fileno)
+ # => #
+ io.path
+ # 3.1: NoMethodError (undefined method `path')
+ # 3.2: => nil
+
+ # IO can't guess file path from the descriptor, but path can be provided explicitly:
+ io = IO.new(f.fileno, path: 'README.md')
+ # => #
+ io.path
+ # => "README.md"
+
+ # One generalization of the new feature was to introspection of standard IO streams:
+ STDOUT.path
+ # 3.1: NoMethodError (undefined method `path')
+ # 3.2: => ""
+ ```
+
+### Exceptions[](#exceptions)
+
+#### `Exception#detailed_message`[](#exceptiondetailed_message)
+
+The method can be redefined for providing custom "decoration" of exception messages, without redefining the main `#message`.
+
+* **Reason:** Standard libraries like `did_you_mean` (adds "did you mean _other name_" to `NoMethodError`) or `error_highlight` (printing of failed part of code and highlighting the problematic part) previously adjusted `Exception#message` method. It might not always be convenient: say, if an application wants to benefit from those gems, but also need to report "clear" error messages to a monitoring system, it required workaround. Starting from Ruby 3.2, there is a clear distinction:
+ * `#message` is an original message with which the exception was raised;
+ * `#decorated_message` might be redefined by some libraries or user's code for convenience and better reporting (most probably);
+ * `#full_message` ([introduced](2.5.html#exceptionfull_message) in 2.5) is what the interpreter prints: detailed message + error backtrace.
+* **Discussion:** Feature #18564
+* **Documentation:** Exception#detailed_message
+* **Code:**
+ ```ruby
+ # Default implementation:
+ begin
+ raise RuntimeError, 'test'
+ rescue => e
+ puts e.message
+ # test
+
+ puts e.detailed_message # adds error class
+ # test (RuntimeError)
+
+ puts e.full_message # adds backtrace
+ # test.rb:3:in `': test (RuntimeError)
+ end
+
+ # NoMethodError employs did_you_mean to lookup for the right name,
+ # and error_highight to show where exactly the error happened:
+ begin
+ 'foo'.lenthg
+ rescue => e
+ puts e.message
+ # undefined method `lenthg' for "foo":String
+
+ puts e.detailed_message # class name + highlighted part of code + "Did you mean?"
+ # undefined method `lenthg' for "foo":String (NoMethodError)
+ #
+ # 'foo'.lenthg
+ # ^^^^^^^
+ # Did you mean? length
+
+ puts e.full_message # all of the above + "where it happened"
+ # test.rb:2:in `': undefined method `lenthg' for "foo":String (NoMethodError)
+ #
+ # 'foo'.lenthg
+ # ^^^^^^^
+ # Did you mean? length
+ end
+
+ # Implement the custom one:
+ class LoadError
+ def detailed_message(highlight: false, **)
+ res = super # invoke the default implementation which will produce message + class name
+ return res unless path.start_with?('vendor/')
+
+ # Provide custom value. Ideally, the code should consider to add some
+ # markup with escape codes for expressiveness if `highlight: true` is passed
+ res + "\n"\
+ " Vendor library `#{path.delete_prefix('vendor/')}' not loaded\n"\
+ " Check our instructions in VENDOR.md"
+ end
+ end
+
+ require 'vendor/tricky'
+ # This will now raise an error which would be printed as...
+ #
+ # in `require': cannot load such file -- vendor/tricky (LoadError)
+ # Vendor library `tricky' not loaded
+ # Check our instructions in VENDOR.md
+ ```
+
+#### `SyntaxError#path`[](#syntaxerrorpath)
+
+Returns the path of where the error have happened.
+
+* **Reason:** The feature was introduced by request of [SyntaxSuggest new core library](#standard-library-content-changes). It makes post-processing of SyntaxError easier for this and third-party libraries, say, when it is necessary to analyze the code that errored.
+* **Discussion:** Feature #19138
+* **Documentation:** SyntaxError
+* **Code:**
+ ```ruby
+ # Consider there is 'test.rb' such that:
+ x = 5
+ y = 6
+ z = x**
+ #----
+
+ begin
+ load 'test.rb'
+ rescue SyntaxError => e
+ p e #=> #
+ puts e.path #=> test.rb
+ end
+ ```
+* **Note:** As of 3.2, there is no way to set `path` (unlike other additional exception data like `KeyError#key` that can be set in `#initialize`). As `SyntaxError` is mostly meant to be generate by Ruby parser and not by custom code, that might not be a big problem.
+
+### Concurrency[](#concurrency)
+
+* For documentation purposes, Fiber::SchedulerInteface (3.0-3.1) was renamed to Fiber::Scheduler (3.2+). It still serves just a documentation-level abstraction: no real class with such name exists, see [our explanations](https://rubyreferences.github.io/rubychanges/3.0.html#non-blocking-fiber-and-scheduler) in 3.0 changelog.
+
+#### `Fiber` storage[](#fiber-storage)
+
+Per-fiber hash-alike storage interface is introduced. It can be set up on Fiber creation, and accessed as a whole via `#storage` accessors, or key-by-key with `Fiber[]` accessors. By default, it is inherited on fiber creation, but can be overridden.
+
+* **Reason:** The official explanation from NEWS is the best: "You should generally consider Fiber storage for any state which you want to be shared implicitly between all fibers and threads created in a given context, e.g. a connection pool, a request id, a logger level, environment variables, configuration, etc."
+* **Discussion:** Feature #19078
+* **Documentation:** Fiber.[], Fiber.[]=, Fiber#storage, Fiber#storage=, Fiber.new
+* **Code:**
+ ```ruby
+ Fiber[:user] = 'admin'
+ Fiber[:user] #=> "admin"
+ Fiber.current.storage #=> {user: 'admin'}
+ # This will have no effect, storage returns a copy of internal storage
+ Fiber.current.storage[:user] = 'John'
+ # Still the same:
+ Fiber[:user] #=> "admin"
+
+ Fiber.current.storage = {user: 'Jane'}
+ # warning: Fiber#storage= is experimental and may be removed in the future!
+ Fiber[:user] #=> "Jane"
+
+ # Cleaning up the storage
+ Fiber.current.storage = nil
+ Fiber.current.storage #=> {}
+
+ Fiber[:user] = 'admin'
+ f = Fiber.new { puts Fiber[:user] }
+ f.resume # prints "admin", by default the storage is inherited
+ f.storage
+ # raises "Fiber storage can only be accessed from the Fiber it belongs to" (ArgumentError)
+
+ # The storage can be overwritten on creation:
+ Fiber.new(storage: {user: 'Jane'}) { puts Fiber[:user] }.resume
+ # prints "Jane"
+ # or...
+ Fiber.new(storage: nil) { puts Fiber[:user] }.resume
+ # prints empty string
+
+ # The same as default: inherit from the creating fiber:
+ Fiber.new(storage: true) { puts Fiber[:user] }.resume
+ # prints "admin"
+
+ # Even if inherited, fiber storage is isolated between fibers:
+ f = Fiber.new {
+ puts Fiber[:user]
+ Fiber[:user] = 'Amy'
+ }
+ Fiber[:user] = 'Jane'
+ f.resume
+ # prints "admin" from fiber, change in the main fiber didn't affect inherited
+ puts Fiber[:user]
+ # prints "Jane", change in inherited fiber didn't affect the main one
+ ```
+* **Notes:**
+ * Only `Fiber#storage=` is considered experimental; the rest of API is considered stable;
+ * There is an [API discrepancy](https://bugs.ruby-lang.org/issues/19377), currently discussed, between `Fiber[]` (which is class method, reading/writing current fiber's storage) and `Fiber.current.storage` (instance method, but available only on class instance);
+
+#### `Fiber::Scheduler#io_select`[](#fiberschedulerio_select)
+
+Implements non-blocking `IO.select`
+
+* **Discussion:** Feature #19060
+* **Documentation:** Fiber::Scheduler#io_select
+* **Notes:**
+ * See code examples in [3.0 changelog](3.0.html#non-blocking-fiber-and-scheduler) for general demo of using Fiber Scheduler. As no simple implementation is available, it is complicated to show an example of new hooks in play.
+ * Just to remind: Ruby does not include the default implementation of Fiber Scheduler, but the maintainer of the feature, Samuel Williams, provides one in his gem Async which is Ruby 3.2-compatible already.
+
+### Internals[](#internals)
+
+#### `Thread.each_caller_location`[](#threadeach_caller_location)
+
+A way for enumerating backtrace entries without instantiating them all.
+
+* **Reason:** There are may contexts when only a small chunk of the backtrace is necessary, but to find this chunk, the whole backtrace needs to be materalized with `#caller_locations`. For example, consider "send to monitoring system the first line in the `app/` that called this (library) query code." In large apps under high load, the call stack might be really large, and cost of its materialization into Ruby objects on frequent calls might be significant. The new method allows to go through stack frames one by one, and break as soon as the necessary one(s) is reached.
+* **Discussion:** Feature #16663
+* **Documentation:** Thread.each_caller_location
+* **Code:**
+ ```ruby
+ # test.rb
+ def inner
+ Thread.each_caller_location {
+ p [_1, _1.class]
+ }
+ end
+
+ def outer
+ inner
+ end
+
+ outer
+ # prints:
+ # ["test.rb:8:in `outer'", Thread::Backtrace::Location]
+ # ["test.rb:11:in `'", Thread::Backtrace::Location]
+
+ # More realistic usage:
+ def method_to_debug
+ # ...
+ app_frame = nil
+ Thread.each_caller_location {
+ if _1.path.match?('/app')
+ app_frame = _1
+ break
+ end
+ }
+ Monitoring.notify "Method was invoked by #{app_frame}"
+ # ...
+ end
+ ```
+* **Notes:**
+ * Note that while each item is printed as a regular string, they are actually instances of an utility class Thread::Backtrace::Location.
+ * The method _consciously_ doesn't have block-less version (which should've returned `Enumerator` as Enumerable's method like `#each` or `#map` do): this would defy the point of efficient backtrace analysis _at the current frame_, adding more frames of Enumerable/Enumerator implementations;
+ * For the reason of efficiency, `each_caller_location` returns nothing (again, to avoid materializing unnecessary objects), so if the goal is to find one location, as in example above, or select some part of the call stack, the only way to do it is non-idiomatic code:
+ ```ruby
+ lib = []
+
+ # Goal: take the first caller locations while they are inside our app's lib/ folder:
+ Thread.each_caller_locaton {
+ lib << _1
+ break unless _1.start_with?('lib/')
+ }
+ ```
+
+#### `GC.latest_gc_info`: add `need_major_gc:` key[](#gclatest_gc_info-add-need_major_gc-key)
+
+* **Reason:** The information (whether the next garbage collection would be _minor_ or _major_) might be useful for highload systems, where it might make sense to trigger garbage-collection preemptively if the next one would be major, before entering the performance-critical part of the code.
+* **Discussion:** GH-6791
+* **Documentation:** GC.latest_gc_info (the possible keys aren't documented)
+* **Code:**
+ ```ruby
+ GC.latest_gc_info
+ # 3.1:
+ # => {:major_by=>nil, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false, :state=>:sweeping}
+ # 3.2:
+ # => {:major_by=>nil, :need_major_by=>nil, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false, :state=>:none}
+
+ # Or:
+ GC.latest_gc_info(:need_major_by) #=> nil
+ ```
+* **Notes:** The author of this changelog is not a GC expert, and the matter is not very well documented, so I only can say that the possible values (besides `nil`), according to feature's code, are `:nofree`, `:oldgen`, `:shady`, `:force`, and they are the same as `major_by:` possibe values. As far as I can guess, `major_by:` describes the latest major GC method, while `need_major_by:` describes the upcoming one; if it is `nil`, the major GC is not upcoming probably?..
+
+#### `ObjectSpace`: dumping object shapes[](#objectspace-dumping-object-shapes)
+
+Object Shapes is a large and interesting new internal object structuring approach which we (being focused on language API) wouldn't explain here. The explanation and discussion can be found at Feature #18776. The only way the Ruby-level API is affected by the change is a new parameter for `ObjectSpace.dump_all` method, that allows to dump _shapes_ defined so far.
+
+* **Discussion:** GH-6868
+* **Documentation:** ObjectSpace#dump_all (docs not fully updated, though)
+* **Code:**
+ ```ruby
+ require 'objspace'
+
+ # To only output what would be put int ObjectSpace since this point
+ gc_generation = GC.count
+ since_id = RubyVM.stat(:next_shape_id)
+
+ # New shapes are defined when instance vars for objects are set, so let's make one!
+ class User
+ def initialize(id, name)
+ @id = id
+ @name = name
+ end
+ end
+
+ User.new(1, 'Yuki')
+
+ ObjectSpace.dump_all(output: :stdout, since: gc_generation, shapes: since_id)
+ # {"address":"0x7f6490e00da0", "type":"SHAPE", "id":237, "parent_id":5, "depth":3, "shape_type":"IVAR","edge_name":"@id", "edges":1, "memsize":120}
+ # {"address":"0x7f6490e00dc0", "type":"SHAPE", "id":238, "parent_id":237, "depth":4, "shape_type":"IVAR","edge_name":"@name", "edges":0, "memsize":32}
+ ```
+ This reads: setting `@id` creates a new shape (with `"id":237`), and setting `@name` creates the next one, inherited from from that (`"id":238, "parent_id":237`). To understand the deep meaning and consequences of this behavior, though, we'll refer to the [original discussion](https://bugs.ruby-lang.org/issues/18776).
+
+#### `TracePoint#binding` returns `nil` for `c_call`/`c_return`[](#tracepointbinding-returns-nil-for-c_callc_return)
+
+* **Reason:** See `Kernel#binding` [explanations above](#kernelbinding-raises-if-accessed-not-from-ruby): C methods don't have their own binding, so before Ruby 3.2, `TracePoint#binding` for their call confusingly returned the binding of the first Ruby caller in the call stack.
+* **Discussion:** Bug #18487
+* **Documentation:** TracePoint#binding (still has docs for old behavior, though)
+* **Code:**
+ ```ruby
+ TracePoint.new(:c_call) do |tp|
+ p [tp.method_id, tp.binding, tp.binding&.local_variables]
+ end.enable {
+ x = [5]
+ x.map { }
+ }
+ # In Ruby 3.1, this prints:
+ # [:map, #, [:x]] -- so, we have a binding of surrounding block, not insides of `map`
+ # In Ruby 3.2:
+ # [:map, nil, nil]
+ ```
+
+#### `TracePoint` for block default to trace the current thread[](#tracepoint-for-block-default-to-trace-the-current-thread)
+
+* **Reason:** In block form, the intention of the developer is to trace what's happening in the specified block. In complicated applications, though, other threads might work at the same time and pollute the tracing with unrelated occurrences.
+* **Discussion:** Bug #16889
+* **Documentation:** TracePoint#enable.
+* **Code:**
+ ```ruby
+ def test = nil
+
+ other = Thread.start {
+ sleep(0.1) # to give TracePoint time to start
+ test
+ }
+
+ Thread.current.name = 'main'
+ other.name = 'other'
+
+ # Note: each example below needs to restart the "other" thread.
+
+ TracePoint.new(:call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :test
+ end.enable do
+ test
+ other.join
+ end
+ # Ruby 3.1:
+ # Called from #
+ # Called from #
+ # Ruby 3.2:
+ # Called from #
+
+ # The desired thread to trace can be specified explicitly:
+ TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end.enable(target_thread: other) do
+ test
+ other.join
+ end
+ # Ruby 3.1 and 3.2:
+ # Called from #
+
+ # Only block form is affected:
+ tp = TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end
+ tp.enable
+
+ test
+ other.join
+ # Ruby 3.1 & 3.2:
+ # Called from #
+ # Called from #
+
+ # If target for tracing is explicitly specified, all threads are traced:
+ TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end.enable(target: method(:test)) do
+ test
+ other.join
+ end
+ # Ruby 3.1 & 3.2:
+ # Called from #
+ # Called from #
+ ```
+
+### `RubyVM::AbstractSyntaxTree`[](#rubyvmabstractsyntaxtree)
+
+#### `error_tolerant: true` option for parsing[](#error_tolerant-true-option-for-parsing)
+
+With this option, parsing can be performed even on incomplete and syntactically incorrect scripts, replacing unparseable parts with `ERROR` token.
+
+* **Reason:** The new option opens road for using Ruby native parser for various language tools working on the fly, while the code is written (like [LSP](https://en.wikipedia.org/wiki/Language_Server_Protocol)) or providing advice and possible fixes on erroneous code. It is important that "official" language parser supported such cases out-of-the-box.
+* **Discussion:** Feature #19013
+* **Documentation:** AbstractSyntaxTree.parse
+* **Code:**
+ ```ruby
+ src = <<~RUBY
+ def test
+ RUBY
+
+ RubyVM::AbstractSyntaxTree.parse(src)
+ # in `parse': syntax error, unexpected end-of-input (SyntaxError)
+
+ root = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true)
+ pp root
+ # Shortening for the sake of this changelog, the structure of the tree would be:
+ #
+ # (SCOPE@1:0-1:8
+ # body:
+ # (DEFN@1:0-1:8
+ # mid: :test
+ # body:
+ # (SCOPE@1:0-1:8
+ # args:
+ # (ARGS@1:8-1:8 ...)
+ # body: nil)))
+ #
+ # E.g. the code is correctly parsed as "a beginning of a method `test`
+ # without a body"
+
+ # The parser also tries to recover from errors in the middle of the script:
+ src = <<~RUBY
+ def bad
+ x +
+ end
+
+ def good
+ puts 'ok'
+ end
+ RUBY
+
+ root = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true)
+ pp root
+ # Shortened output again...
+ #
+ # (SCOPE@1:0-7:3
+ # body:
+ # (BLOCK@1:0-7:3
+ # (DEFN@1:0-3:3
+ # mid: :bad
+ # body:
+ # (SCOPE@1:0-3:3
+ # body: (ERROR@2:2-3:3)))
+ # (DEFN@5:0-7:3
+ # mid: :good
+ # body:
+ # (SCOPE@5:0-7:3
+ # body: (FCALL@6:2-6:13 :puts (LIST@6:7-6:13 (STR@6:7-6:13 "test") nil))))))
+ #
+ # Note the ERROR node in the midle of method :bad, but then properly parsed
+ # body of method :good
+ ```
+* **Notes:**
+ * Recovery not guaranteed
+
+#### `keep_tokens: true` option for parsing[](#keep_tokens-true-option-for-parsing)
+
+With `keep_tokens: true` option provided, `AbstractSyntaxTree.parse` will attach corresponding code tokens array to each node of the syntax tree.
+
+* **Reason:** As the previous feature, this one is useful for implementing code analysis tools: there are several ways to write code that will produce exactly the same syntax tree; and while it doesn't affect interpreting, it does affect style checking, suggestions etc.
+* **Discussion:** Feature #19070
+* **Documentation:** AbstractSyntaxTree.parse, Node#tokens, Node#all_tokens
+* **Code:**
+ ```ruby
+ RubyVM::AbstractSyntaxTree.parse("puts 'test'", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :tSP, " ", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]]]
+ RubyVM::AbstractSyntaxTree.parse("puts('test')", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :"(", "(", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]],
+ # [5, :")", ")", [1, 11, 1, 12]]]
+ RubyVM::AbstractSyntaxTree.parse("puts('test', )", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :"(", "(", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]],
+ # [5, :",", ",", [1, 11, 1, 12]],
+ # [6, :tSP, " ", [1, 12, 1, 13]],
+ # [7, :")", ")", [1, 13, 1, 14]]]
+ ```
+ Note that all three scripts are exactly equivalent execution-wise and will produce the same syntax tree; but from the point of view of code analysis tool, they are different. For example, the first one might cause the suggestion to add parentheses (if that's the preferred style setting), and the last one might imply that the user waits for suggestions for possible local variables to add to output.
+
+## Standard library[](#standard-library)
+
+By Ruby 3.1 release, most of the standard library is extracted to either _default_ or _bundled_ gems; their development happens in separate repositories, and changelogs are either maintained there, or absent altogether. Either way, their changes aren't mentioned in the combined Ruby changelog, and I'll not be trying to follow all of them.
+
+> **[stdgems.org](https://stdgems.org/)** project has a nice explanations of default and bundled gems concepts, as well as a list of currently gemified libraries and links to their docs.
+
+> "For the rest of us" this means libraries development extracted into separate GitHub repositories, and they are just packaged with main Ruby before release. It means you can do issue/PR to any of them independently, without going through more tough development process of the core Ruby.
+
+A few changes to mention, though:
+
+* Pathname#lutime.
+* FileUtils.ln_sr and `relative:` option for FileUtils.ln_s. Discussion: Feature #18925.
+* CGI.escapeURIComponent and CGI.unescapeURIComponent are added. This is an attempt to mitigate discrepancy between various helper method throughout the standard libraries like `URI`, `ERB` and `CGI`. Discussion: Feature #18822
+ * The difference with `CGI.escape`/`unescape` is only in encoding and decoding `' '` character (`escape` follows `application/x-www-form-urlencoded` which converts it to `+`, while `escapeURIComponent` follows RFC 3986 and converts it to `'%20'`)
+ * Previously, the goal could've been achieved with `URI.escape`, but it was deprecated since 1.9 and removed in 3.0, being too vague and generic (it actually meant to replace all "unsafe" characters on URI construction).
+ * Unusual for Ruby method names are mimicking well-known JS ones like [encodeURIComponent](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).
+* `Coverage`:
+ * The internal changes in interpreter were made so the standard library would be able to measure code coverage of `eval`-ed code.
+ * Discussion: Feature #19008.
+ * Docs Coverage.setup (enabled with `eval: true`, or `:all` arguments)
+ * .supported?. Discussion: Feature #19026
+* There are many awesome changes in Ruby's console IRB, see the gem author's article [What's new in Ruby 3.2's IRB?](https://st0012.dev/whats-new-in-ruby-3-2-irb).
+
+### Version updates[](#version-updates)
+
+#### Default gems[](#default-gems)
+
+* RubyGems 3.4.1
+* abbrev 0.1.1
+* benchmark 0.2.1
+* bigdecimal 3.1.3
+* bundler 2.4.1
+* cgi 0.3.6
+* csv 3.2.6
+* date 3.3.3
+* delegate 0.3.0
+* did_you_mean 1.6.3
+* digest 3.1.1
+* drb 2.1.1
+* english 0.7.2
+* erb 4.0.2
+* error_highlight 0.5.1
+* etc 1.4.2
+* fcntl 1.0.2
+* fiddle 1.1.1
+* fileutils 1.7.0
+* forwardable 1.3.3
+* getoptlong 0.2.0
+* io-console 0.6.0
+* io-nonblock 0.2.0
+* io-wait 0.3.0
+* ipaddr 1.2.5
+* irb 1.6.2
+* json 2.6.3
+* logger 1.5.3
+* mutex_m 0.1.2
+* net-http 0.3.2
+* net-protocol 0.2.1
+* nkf 0.1.2
+* open-uri 0.3.0
+* open3 0.1.2
+* openssl 3.1.0
+* optparse 0.3.1
+* ostruct 0.5.5
+* pathname 0.2.1
+* pp 0.4.0
+* pstore 0.1.2
+* psych 5.0.1
+* racc 1.6.2
+* rdoc 6.5.0
+* readline-ext 0.1.5
+* reline 0.3.2
+* resolv 0.2.2
+* resolv-replace 0.1.1
+* securerandom 0.2.2
+* stringio 3.0.4
+* strscan 3.0.5
+* syntax_suggest 1.0.2
+* syslog 0.1.1
+* tempfile 0.1.3
+* time 0.2.1
+* timeout 0.3.1
+* tmpdir 0.1.3
+* tsort 0.1.1
+* un 0.2.1
+* uri 0.12.0
+* weakref 0.1.2
+* win32ole 1.8.9
+* yaml 0.2.1
+* zlib 3.0.0
+
+#### Bundled gems[](#bundled-gems)
+
+* minitest 5.16.3
+* power_assert 2.0.3
+* test-unit 3.5.7
+* net-ftp 0.2.0
+* net-imap 0.3.4
+* net-pop 0.1.2
+* net-smtp 0.3.3
+* rbs 2.8.2
+* typeprof 0.21.3
+* debug 1.7.1
+
+### Standard library content changes[](#standard-library-content-changes)
+
+* syntax_suggest (formerly `dead_end`) gem added. It provides helpful error messages for wrong syntax, trying to guess the place of the error. For example, assuming this `test.rb`:
+ ```ruby
+ def foo
+ [1, 2, 3].each {
+ end
+ ```
+ an attempt to run it with Ruby 3.1 produces:
+ ```
+ test.rb:3: syntax error, unexpected `end'
+ ```
+ while Ruby 3.2 produces:
+ ```
+ Unmatched `{', missing `}' ?
+ 23 def foo
+ > 24 [1, 2, 3].each {
+ 25 end
+ test.rb:3: syntax error, unexpected `end' (SyntaxError)
+ ```
+
diff --git a/Gemfile.lock b/Gemfile.lock
index cd2f69f..37806f1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -227,15 +227,17 @@ GEM
rb-inotify (~> 0.9, >= 0.9.10)
memoist (0.16.2)
mercenary (0.3.6)
- mini_portile2 (2.7.1)
+ mini_portile2 (2.8.1)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.15.0)
multipart-post (2.1.1)
- nokogiri (1.13.1)
- mini_portile2 (~> 2.7.0)
+ nokogiri (1.14.0)
+ mini_portile2 (~> 2.8.0)
+ racc (~> 1.4)
+ nokogiri (1.14.0-x86_64-linux)
racc (~> 1.4)
octokit (4.22.0)
faraday (>= 0.9)
@@ -243,7 +245,7 @@ GEM
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
- racc (1.6.0)
+ racc (1.6.2)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
@@ -280,6 +282,7 @@ GEM
PLATFORMS
ruby
+ x86_64-linux
DEPENDENCIES
github-pages
@@ -288,4 +291,4 @@ DEPENDENCIES
rake
BUNDLED WITH
- 1.17.2
+ 2.2.0
diff --git a/History.md b/History.md
index f7899fa..2836ab7 100644
--- a/History.md
+++ b/History.md
@@ -2,6 +2,11 @@
(Only outstanding content changes listed, the texts and code are constantly updated and fixed thanks to awesome contributors.)
+## 2023-02-04
+
+* **[3.2](3.2.html)** changelog added. "Better late than never," war year edition!
+* [Ruby Evolution](evolution.html) updated accordingly.
+
## 2022-06-09
* **[Ruby Evolution](evolution.html)** bird-eye view added.
diff --git a/Rakefile b/Rakefile
index eebdf7e..39c8cea 100644
--- a/Rakefile
+++ b/Rakefile
@@ -20,8 +20,10 @@ rule /^(\d+\.\d+|evolution)\.md$/ => ->(s) { "_src/#{s}" } do |t|
File.write(to, Render.(from))
end
+VERSIONS = [*('2.4'..'2.7'), *('3.0'..'3.2')]
+
desc 'Convert file contents from source to target (prettify)'
-task contents: ['evolution', *('2.4'..'2.7'), *('3.0'..'3.1')].map(&'%s.md'.method(:%))
+task contents: ['evolution', *VERSIONS].map(&'%s.md'.method(:%))
desc 'Render TOC for the changelog "book"'
task toc: '_data/book.yml'
diff --git a/_data/book.yml b/_data/book.yml
index 605eb1d..a7a5510 100644
--- a/_data/book.yml
+++ b/_data/book.yml
@@ -31,6 +31,8 @@ chapters:
path: "/evolution.html#strings-symbols-regexps-encodings"
- title: "Struct"
path: "/evolution.html#struct"
+ - title: "Data"
+ path: "/evolution.html#data"
- title: "Time"
path: "/evolution.html#time"
- title: Enumerables, collections, and iteration
@@ -77,8 +79,12 @@ chapters:
path: "/evolution.html#gc"
- title: "TracePoint"
path: "/evolution.html#tracepoint"
+ - title: "RubyVM::AbstractSyntaxTree"
+ path: "/evolution.html#rubyvmabstractsyntaxtree"
- title: "RubyVM::InstructionSequence"
path: "/evolution.html#rubyvminstructionsequence"
+ - title: "ObjectSpace"
+ path: "/evolution.html#objectspace"
- title: Deeper topics
path: "/evolution.html#deeper-topics"
children:
@@ -88,6 +94,170 @@ chapters:
path: "/evolution.html#freezing"
- title: 'Appendix: Covered Ruby versions release dates'
path: "/evolution.html#appendix-covered-ruby-versions-release-dates"
+- title: Ruby 3.2
+ path: "/3.2.html"
+ is_version: true
+ published_at: '2022-02-04'
+ description: |
+ Highlights:
+
+
+ - Anonymous method argument passing
+ - More inspectable refinements
+ Data class
+ - Support for pattern-matching in
Time and MatchData
+ Set is a built-in class
+ - Per-
Fiber storage
+ RubyVM::AbstractSyntaxTree: fault-tolerant and token-level parsing
+
+
+ Read more »
+ children:
+ - title: Highlights
+ path: "/3.2.html#highlights"
+ - title: Language changes
+ path: "/3.2.html#language-changes"
+ children:
+ - title: Anonymous arguments passing improvements
+ path: "/3.2.html#anonymous-arguments-passing-improvements"
+ - title: Constant assignment evaluation order changed
+ path: "/3.2.html#constant-assignment-evaluation-order-changed"
+ - title: Behavior of module reopening/redefinition with included modules changed
+ path: "/3.2.html#behavior-of-module-reopeningredefinition-with-included-modules-changed"
+ - title: Keyword argument separation leftovers
+ path: "/3.2.html#keyword-argument-separation-leftovers"
+ - title: Removals
+ path: "/3.2.html#removals"
+ - title: Core classes and modules
+ path: "/3.2.html#core-classes-and-modules"
+ children:
+ - title: "Kernel#binding raises if accessed not from Ruby"
+ path: "/3.2.html#kernelbinding-raises-if-accessed-not-from-ruby"
+ - title: Class and Module
+ path: "/3.2.html#class-and-module"
+ children:
+ - title: "Class#attached_object"
+ path: "/3.2.html#classattached_object"
+ - title: "Module#const_added"
+ path: "/3.2.html#moduleconst_added"
+ - title: "Module#undefined_instance_methods"
+ path: "/3.2.html#moduleundefined_instance_methods"
+ - title: Refinements
+ path: "/3.2.html#refinements"
+ children:
+ - title: "Module#refinements"
+ path: "/3.2.html#modulerefinements"
+ - title: "Refinement#refined_class"
+ path: "/3.2.html#refinementrefined_class"
+ - title: "Module.used_refinements"
+ path: "/3.2.html#moduleused_refinements"
+ - title: Integer#ceildiv
+ path: "/3.2.html#integerceildiv"
+ - title: Strings and regexps
+ path: "/3.2.html#strings-and-regexps"
+ children:
+ - title: Byte-oriented methods
+ path: "/3.2.html#byte-oriented-methods"
+ - title: String#dedup as an alias for -"string"
+ path: "/3.2.html#stringdedup-as-an-alias-for--string"
+ - title: "Regexp.new: passing flags as a string is supported"
+ path: "/3.2.html#regexpnew-passing-flags-as-a-string-is-supported"
+ - title: "Regexp: ReDoS vulnerability prevention"
+ path: "/3.2.html#regexp-redos-vulnerability-prevention"
+ - title: "Time.new can parse a string"
+ path: "/3.2.html#timenew-can-parse-a-string"
+ - title: "Struct and Data"
+ path: "/3.2.html#struct-and-data"
+ children:
+ - title: "Struct can be initialized by keyword arguments by default"
+ path: "/3.2.html#struct-can-be-initialized-by-keyword-arguments-by-default"
+ - title: "Data: new immutable value object class"
+ path: "/3.2.html#data-new-immutable-value-object-class"
+ - title: Pattern matching
+ path: "/3.2.html#pattern-matching"
+ children:
+ - title: "MatchData: added #deconstruct and #deconstruct_keys"
+ path: "/3.2.html#matchdata-added-deconstruct-and-deconstruct_keys"
+ - title: "Time#deconstruct_keys"
+ path: "/3.2.html#timedeconstruct_keys"
+ - title: Enumerables and collections
+ path: "/3.2.html#enumerables-and-collections"
+ children:
+ - title: "Enumerator.product"
+ path: "/3.2.html#enumeratorproduct"
+ - title: "Hash#shift always returns nil if the hash
+ is empty"
+ path: "/3.2.html#hashshift-always-returns-nil-if-the-hash-is-empty"
+ - title: "Set became a built-in class"
+ path: "/3.2.html#set-became-a-built-in-class"
+ - title: "Thread::Queue: timeouts for pop and push"
+ path: "/3.2.html#threadqueue-timeouts-for-pop-and-push"
+ - title: Procs and methods
+ path: "/3.2.html#procs-and-methods"
+ children:
+ - title: "Proc#dup returns an instance of subclass"
+ path: "/3.2.html#procdup-returns-an-instance-of-subclass"
+ - title: "Proc#parameters: new keyword argument lambda: true/false"
+ path: "/3.2.html#procparameters-new-keyword-argument-lambda-truefalse"
+ - title: "Method#public?, #protected?, and #private?
+ are removed"
+ path: "/3.2.html#methodpublic-protected-and-private-are-removed"
+ - title: "UnboundMethod: more consistent reporting on what module
+ it belongs to"
+ path: "/3.2.html#unboundmethod-more-consistent-reporting-on-what-module-it-belongs-to"
+ - title: IO and network
+ path: "/3.2.html#io-and-network"
+ children:
+ - title: "IO: support for timeouts for blocking IO"
+ path: "/3.2.html#io-support-for-timeouts-for-blocking-io"
+ - title: "IO#path"
+ path: "/3.2.html#iopath"
+ - title: Exceptions
+ path: "/3.2.html#exceptions"
+ children:
+ - title: "Exception#detailed_message"
+ path: "/3.2.html#exceptiondetailed_message"
+ - title: "SyntaxError#path"
+ path: "/3.2.html#syntaxerrorpath"
+ - title: Concurrency
+ path: "/3.2.html#concurrency"
+ children:
+ - title: "Fiber storage"
+ path: "/3.2.html#fiber-storage"
+ - title: "Fiber::Scheduler#io_select"
+ path: "/3.2.html#fiberschedulerio_select"
+ - title: Internals
+ path: "/3.2.html#internals"
+ children:
+ - title: "Thread.each_caller_location"
+ path: "/3.2.html#threadeach_caller_location"
+ - title: "GC.latest_gc_info: add need_major_gc: key"
+ path: "/3.2.html#gclatest_gc_info-add-need_major_gc-key"
+ - title: "ObjectSpace: dumping object shapes"
+ path: "/3.2.html#objectspace-dumping-object-shapes"
+ - title: "TracePoint#binding returns nil for c_call/c_return"
+ path: "/3.2.html#tracepointbinding-returns-nil-for-c_callc_return"
+ - title: "TracePoint for block default to trace the current thread"
+ path: "/3.2.html#tracepoint-for-block-default-to-trace-the-current-thread"
+ - title: "RubyVM::AbstractSyntaxTree"
+ path: "/3.2.html#rubyvmabstractsyntaxtree"
+ children:
+ - title: "error_tolerant: true option for parsing"
+ path: "/3.2.html#error_tolerant-true-option-for-parsing"
+ - title: "keep_tokens: true option for parsing"
+ path: "/3.2.html#keep_tokens-true-option-for-parsing"
+ - title: Standard library
+ path: "/3.2.html#standard-library"
+ children:
+ - title: Version updates
+ path: "/3.2.html#version-updates"
+ children:
+ - title: Default gems
+ path: "/3.2.html#default-gems"
+ - title: Bundled gems
+ path: "/3.2.html#bundled-gems"
+ - title: Standard library content changes
+ path: "/3.2.html#standard-library-content-changes"
- title: Ruby 3.1
path: "/3.1.html"
is_version: true
diff --git a/_src/2.5.md b/_src/2.5.md
index 00e390e..b9dbf3e 100644
--- a/_src/2.5.md
+++ b/_src/2.5.md
@@ -145,7 +145,7 @@ When `uplevel: N` provided to `Kernel#warn`, the warning message includes the fi
Ruby 2.4 introduced redefinable `Warning.warn` method, which allowed to control how warnings are handled. But `warn` method wasn't calling it, so initially only warnings issued from C code could've been handled. 2.5 fixes it. (Was not mentioned in NEWS-2.5.0)
-* **Discussion:** [Feature #12299, comment #14-15](https://bugs.ruby-lang.org/issues/12299#note-14)
+* **Discussion:** [Feature #12299#note-14](https://bugs.ruby-lang.org/issues/12299#note-14)
* **Documentation:** —
* **Code:**
```ruby
@@ -375,7 +375,9 @@ The underlying [Onigmo](https://github.com/k-takata/Onigmo) Regexp library was u
ModernPerson.new('Meredith', 'Williams', 28)
# ArgumentError (wrong number of arguments (given 3, expected 0))
```
-* **Follow-up:** Ruby 3.1 introduced [a method](3.1.html#structclasskeyword_init) to check whether some struct should be initialized with keyword arguments, and [a warning](3.1.html#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) on erroneous initialization of non-keyword-args struct with a hash.
+* **Follow-ups:**
+ * Ruby 3.1 introduced [a method](3.1.html#structclasskeyword_init) to check whether some struct should be initialized with keyword arguments, and [a warning](3.1.html#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) on erroneous initialization of non-keyword-args struct with a hash.
+ * Ruby 3.2 [allowed](3.2.html#struct-can-be-initialized-by-keyword-arguments-by-default) all structs without explicit `keyword_init:` parameter specified to be initialized by both positional and keyword args.
### `Time.at`: units
@@ -637,6 +639,11 @@ The constant `TMPFILE` is [documented](https://ruby-doc.org/core-2.5.0/File/Cons
# Ruby 2.4: => "/tmp" -- what???
# Ruby 2.5: IOError (File is unnamed (TMPFILE?))
```
+* **Follow-up:** Ruby 3.2 [introduced](3.2.html#iopath) a concept of generic `IO#path` which is allowed to be `nil` when the path is unknown, so the behavior changed:
+ ```ruby
+ f = File.open('/tmp', File::RDWR | File::TMPFILE)
+ f.path #=> nil
+ ```
#### `File.lutime`
@@ -698,7 +705,7 @@ Much akin to `Hash#fetch`, allows to fetch thread-local variable or provide defa
### Misc
* `Random.raw_seed` renamed to [Random.urandom](https://ruby-doc.org/core-2.5.0/Random.html#method-c-urandom). Discussion: [Bug #9569](https://bugs.ruby-lang.org/issues/9569).
-* `Data` is deprecated. It was a base class for C extensions, and it's not necessary to expose in Ruby level. [Feature #3072](https://bugs.ruby-lang.org/issues/3072). **Follow-up:** Fully removed in 3.0.
+* `Data` is deprecated. It was a base class for C extensions, and it's not necessary to expose in Ruby level. [Feature #3072](https://bugs.ruby-lang.org/issues/3072). **Follow-up:** Fully removed in 3.0; in 3.2, a new class with the same name but different meaning was [reintroduced](3.2.html#data-new-immutable-value-object-class).
## Standard library
diff --git a/_src/2.6.md b/_src/2.6.md
index cf5e835..384b2a4 100644
--- a/_src/2.6.md
+++ b/_src/2.6.md
@@ -472,6 +472,7 @@ Several enumerators can now be chained into one with `Enumerator#+(other)` or `E
# or redirected with `ruby test.rb 2> err.log
end
```
+* **Follow-up:** In Ruby 3.2, one more related method `#detailed_message` [was added](3.2.html#exceptiondetailed_message).
#### Exception output tweaking
@@ -597,7 +598,10 @@ The new module provides an "official" Ruby parser, replacing stdlib Ripper (and
tree.children[2].children[0].children
# => [1]
```
-* **Notice:** One may be excited about `RubyVM::AbstractSyntaxTree.of(proc)`, but it doesn't mean the "real" extraction of code from live `Proc`, rather a simple hack with trying to find proc's source file and parse it.
+* **Note:** One may be excited about `RubyVM::AbstractSyntaxTree.of(proc)`, but it doesn't mean the "real" extraction of code from live `Proc`, rather a simple hack with trying to find proc's source file and parse it.
+* **Follow-up:** In Ruby 3.2, new options were added for `parse`, allowing to:
+ * Perform [fault-tolerant parsing](3.2.html#error_tolerant-true-option-for-parsing) (parse syntactically wrong/incomplete code);
+ * [Preserve tokens](3.2.html#keep_tokens-true-option-for-parsing) from source code alongside nodes;
### `TracePoint` improvements
@@ -669,6 +673,9 @@ With new parameters provided, you can fine-tune for what methods or specific lin
```
* **Notice:** In absence of docs, we can at least point at [commit message](https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/66003):
> `code` should be consisted of `InstructionSequence` (iseq) (`RubyVM::InstructionSequence.of(code)` should not return nil). If code is a tree of iseq, `TracePoint` is enabled on all of iseqs in a tree.
+* **Follow-ups:**
+ * In 2.7, [the docs](https://docs.ruby-lang.org/en/2.7.0/TracePoint.html#method-i-enable) have emerged, and a new parameter named `target_thread:` was introduced. It was missing from the official `NEWS`-file and therefore missing from this changelog _(which is a thing to be fixed!)_
+ * In 3.2, `target_thread:` began [defaulting to the current thread](3.2.html#tracepoint-for-block-default-to-trace-the-current-thread) with block form of `enable`.
## Standard library
diff --git a/_src/2.7.md b/_src/2.7.md
index d0eaa45..554c693 100644
--- a/_src/2.7.md
+++ b/_src/2.7.md
@@ -8,7 +8,7 @@ description: Ruby 2.7 full and annotated changelog
# Ruby 2.7
* **Released at:** Dec 25, 2019 ([NEWS](https://github.com/ruby/ruby/blob/ruby_2_7/NEWS) file)
-* **Status (as of <>):** 2.7.6 is current _stable_
+* **Status (as of <>):** 2.7.7 is current _stable_
* **This document first published:** Dec 27, 2019
* **Last change to this document:** <>
@@ -48,7 +48,10 @@ data in [{user: {login:}, title:, created_at:}, *] # match array of hashes, with
# => ["zverok", "Add pattern matching documentation", "2019-12-25T18:42:03Z"]
```
-* **Follow-up:** Pattern matching became a stable (non-experimental) feature, and its power expanded signficantly [in 3.0](#pattern-matching); and then it became even more flexible [in 3.1](3.1.html#pattern-matching).
+* **Follow-ups:**
+ * Pattern matching became a stable (non-experimental) feature, and its power expanded signficantly [in 3.0](#pattern-matching);
+ * Then, it became even more flexible [in 3.1](3.1.html#pattern-matching);
+ * In 3.2, several core and standard library classes (`MatchData`, `Time`, `Date`, `DateTime`) [became deconstructible](3.2.html#pattern-matching).
### Keyword argument-related changes
diff --git a/_src/3.0.md b/_src/3.0.md
index d204bad..e7bdcbe 100644
--- a/_src/3.0.md
+++ b/_src/3.0.md
@@ -8,7 +8,7 @@ description: Ruby 3.0 full and annotated changelog
# Ruby 3.0
* **Released at:** Dec 25, 2020 ([NEWS.md](https://github.com/ruby/ruby/blob/ruby_3_0/NEWS.md) file)
-* **Status (as of <>):** 3.0.4 is current _stable_
+* **Status (as of <>):** 3.0.5 is current _stable_
* **This document first published:** Dec 25, 2020
* **Last change to this document:** <>
@@ -76,6 +76,7 @@ Just a leftover from the separation of keyword arguments.
# Ruby 3.0:
# args=[1, 2, {:a=>true}], kwargs={} -- no attempt to extract hash into keywords, and no error/warning
```
+* **Follow-up:** In Ruby 3.2, one more proc argument splatting behavior [was improved](3.2.html#keyword-argument-separation-leftovers).
### Arguments forwarding (`...`) supports leading arguments
@@ -126,6 +127,9 @@ Just a leftover from the separation of keyword arguments.
delegates(5)
```
+* **Follow-ups:**
+ * In Ruby 3.1, a separate anonymous block argument (bare `&`) forwarding [was added](3.1.html#anonymous-block-argument);
+ * In Ruby 3.2, separate positional and keyword (bare `*` and `**`) forwarding [were added](3.2.html#anonymous-arguments-passing-improvements).
### "Endless" method definition
@@ -357,6 +361,7 @@ Pattern matching now supports "find patterns", with several splats in them.
```
* **Notes:**
* Feature is marked as EXPERIMENTAL, will warn so on an attempt of usage, and may change in the future.
+* **Follow-ups:** Feature considered not experimental since 3.2
### Changes in class variable behavior
@@ -1030,6 +1035,7 @@ The method will return true for separate Proc instances if the procs were create
```
* **Notes:**
* See [Ractors](#ractors) explanation below, and [Ractor](https://docs.ruby-lang.org/en/3.0.0/Ractor.html) class docs for deeper understanding of ractors data sharing model.
+* **Follow-up:** In 3.2, the constant was [removed](3.2.html#removals).
### Filesystem and IO
@@ -1127,7 +1133,9 @@ The improvement of inter-thread concurrency for IO-heavy tasks is achieved with
of blocking the whole thread, they were transferring control while waiting, and all three waits are
performed in parallel.
* **Notes:** The feature is somewhat unprecedented for Ruby in the fact that **no default Scheduler implementation** is provided. Implementing the Scheduler in a reliable way (probably using some additional non-blocking event loop library) is completely up to the user. Considering that the feature is implemented by [Samuel Williams](https://github.com/ioquatix) of the [Async](https://github.com/socketry/async) fame, the Async gem [utilizes](https://github.com/socketry/async/blob/master/lib/async/scheduler.rb) the new feature since the day of 3.0 release.
-* **Follow-up:** In Ruby 3.1, [more scheduler hooks were added](3.1.html#fiber-scheduler-new-hooks) to make more core methods support asynchronous execution.
+* **Follow-up:s**
+ * In Ruby 3.1, [more scheduler hooks were added](3.1.html#fiber-scheduler-new-hooks) to make more core methods support asynchronous execution;
+ * In 3.2, [even more](3.2.html#fiberschedulerio_select) hooks were added, and `SchedulerInteface` documentation abstraction was renamed to `Scheduler`.
#### `Thread.ignore_deadlock` accessor
diff --git a/_src/3.1.md b/_src/3.1.md
index 8194c4b..94e3b64 100644
--- a/_src/3.1.md
+++ b/_src/3.1.md
@@ -1,14 +1,14 @@
---
title: Ruby 3.1 changes
-prev: /
-next: 3.0
+prev: 3.2
+next: 3.1
description: Ruby 3.1 full and annotated changelog
---
# Ruby 3.1
* **Released at:** Dec 25, 2021 ([NEWS.md](https://github.com/ruby/ruby/blob/ruby_3_1/NEWS.md) file)
-* **Status (as of <>):** 3.1.2 is current _stable_
+* **Status (as of <>):** 3.1.3 is current _stable_
* **This document first published:** Jan 5, 2022
* **Last change to this document:** <>
@@ -106,7 +106,7 @@ In hash literals and method calls, `x:` is now a shortcut for `x: x`—take hash
If method uses its block argument only to pass to another method, it can be marked by anonymous `&`.
-* **Reason:** The initial proposal for the feature is 6-year old and focused on avoiding intermediate blocks object allocation on block forwarding. It was considered redundant when block forwarding was optimized in Ruby 2.5; but then the core team decided it is actually a nice and unambiguous shortcut for methods that just pass the block further. As bloc argument is frequently called just `block`, the absence of the name doesn't affect readability.
+* **Reason:** The initial proposal for the feature is 6-year old and focused on avoiding intermediate blocks object allocation on block forwarding. It was considered redundant when block forwarding was optimized in Ruby 2.5; but then the core team decided it is actually a nice and unambiguous shortcut for methods that just pass the block further. As block argument is frequently called just `block`, the absence of the name doesn't affect readability.
* **Discussion:** [Feature #11256](https://bugs.ruby-lang.org/issues/11256)
* **Documentation:** [doc/syntax/methods.rdoc#Block Argument](https://docs.ruby-lang.org/en/3.1/syntax/methods_rdoc.html#label-Block+Argument)
* **Code:**
@@ -333,6 +333,7 @@ The class of the context (`self`) available inside the `refine SomeClass do ...
end
end
```
+* **Follow-ups:** In Ruby 3.2, several usability improvments using the new class [were introduced](3.2.html#refinements): ability to ask which refinements some module defines, which refinements are active in the current context, and what class or module the particular refinement refines.
#### `Module#prepend` behavior change
@@ -442,6 +443,7 @@ Now, when module is prepended to the class, it always becomes first in the ances
# => true
```
* **Notes:** Another idea discussed was just having `Method#visibility`, but it was hard to decide on the method's name and what it should return, while three predicates are more or less obvious.
+* **Follow-up:** [Reverted](3.2.html#methodpublic-protected-and-private-are-removed) in Ruby 3.2: it was considered after all that visibility is not a property of the method, but rather the responsibility of its owner.
### `Kernel#load`: module as a second argument
@@ -729,6 +731,7 @@ The new parameter is accepting offsets or timezone objects, and (finally!) allow
#=> #"користувач", :application=>"застосунок"}>
# ...no braces necessary, no warning
```
+* **Follow-up:** Since 3.2, following the warning, the structs [can be initialized](3.2.html#struct-can-be-initialized-by-keyword-arguments-by-default) by both keyword and positional argument when `keyword_init: true` is not defined.
#### `StructClass#keyword_init?`
@@ -895,8 +898,6 @@ A new class representing low-level I/O abstraction. Internally, uses OS mechanis
* **Discussion:** [Feature #18020](https://bugs.ruby-lang.org/issues/18020)
* **Documentation:** [IO::Buffer](https://docs.ruby-lang.org/en/3.1/IO/Buffer.html)
* **Code:** _This is a big new class, see class' docs for detailed examples of usage, they are quite succinct._
-* **Notes:**
-* **Follow-up:**
### `File.dirname`: optional `level` to go up the directory tree
diff --git a/_src/3.2.md b/_src/3.2.md
new file mode 100644
index 0000000..69039bf
--- /dev/null
+++ b/_src/3.2.md
@@ -0,0 +1,1844 @@
+---
+title: Ruby 3.2 changes
+prev: /
+next: 3.1
+description: Ruby 3.2 full and annotated changelog
+---
+
+# Ruby 3.2
+
+* **Released at:** Dec 25, 2022 ([NEWS.md](https://github.com/ruby/ruby/blob/ruby_3_2/NEWS.md) file)
+* **Status (as of <>):** 3.2.0 is current _stable_
+* **This document first published:** Feb 4, 2022
+* **Last change to this document:** <>
+
+
+
+**🇺🇦 🇺🇦 Before you start reading the changelog: A full-scale Russian invasion into my home country continues. The only reason I am alive and able to work on the changelog is Armed Force of Ukraine, and international support with weaponry, funds and information. I am in my home city Kharkiv, preparing to join the army. Please care to read two of my appeals to Ruby community before proceeding: [first](https://zverok.space/blog/2022-03-03-WAR.html), [second](https://zverok.space/blog/2022-03-15-STILL-WAR.html).
We need all support we can get to push inviders out and to bring peace to our land. Please [spread information, lobby our cause and donate](https://war.ukraine.ua/).🇺🇦 🇺🇦**
+
+> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important optimization introduced in 3.2, including YJIT improvements and [object shapes](https://bugs.ruby-lang.org/issues/18776). That's not because they are not important, just because this site's goals are different.
+
+In preparation of this entry, [Cookpad's article](https://techlife.cookpad.com/entry/2022/12/26/121950) on notable changes in Ruby 3.2 written by core developers Koichi Sasada (ko1) and Yusuke Endoh (mame) provided invaluable insight. Thank you!
+
+## Highlights
+
+* [Anonymous method argument passing](#anonymous-arguments-passing-improvements)
+* [More inspectable refinements](#refinements)
+* [`Data` class](#data-new-immutable-value-object-class)
+* [Support for pattern-matching in `Time` and `MatchData`](#pattern-matching)
+* [`Set` is a built-in class](#set-became-a-built-in-class)
+* [Per-`Fiber` storage](#fiber-storage)
+* [`RubyVM::AbstractSyntaxTree`: fault-tolerant and token-level parsing](#rubyvmabstractsyntaxtree)
+
+## Language changes
+
+### Anonymous arguments passing improvements
+
+If the method declaration includes anonymous positional or keyword arguments (`*` or `**` without associated name), those arguments can now be passed to the next method with `some_method(*)` or `some_method(**)` syntax.
+
+* **Reason:** While it can be considered too cryptic shorcut by some, the new feature is consistent with passing of _all_ arguments with `...`.
+* **Discussion:** [Feature #18351](https://bugs.ruby-lang.org/issues/18351)
+* **Documentation:** [Methods: Array/Hash Argument](https://docs.ruby-lang.org/en/3.2/syntax/methods_rdoc.html#label-Array-2FHash+Argument)
+* **Code:**
+ ```ruby
+ def only_keywords(**) # accept keyword arguments
+ p(**) # and pass them to the next method
+ end
+
+ def only_positional(*) # accept positional arguments
+ p(*) # and pass them to the next method
+ end
+
+ def both(*, **) # effectively the same as ...
+ p(*, **)
+ end
+
+ only_keywords(a: 1, b: 2)
+ # prints "{:a=>1, :b=>2}"
+ only_positional(1, 2, 3)
+ # prints
+ # 1
+ # 2
+ # 3
+ both(1, 2, 3, a: :b)
+ # prints
+ # 1
+ # 2
+ # 3
+ # {:a=>:b}
+
+ # Realistic usage: a small wrapper method, just "fall through" to the next one
+ def get(url, **) = send_request(:get, url, **)
+
+ # Named and unnamed could be freely mixed:
+ def mixed_naming(*a, **)
+ p a
+ p(**)
+ end
+
+ mixed_naming(1, 2, 3, a: :b)
+ # prints:
+ # [1, 2, 3]
+ # {:a=>:b}
+
+ # But using anonymous forwarding with named arguments is an error
+ def forward(*args) = p(*)
+ # no anonymous rest parameter (SyntaxError)
+
+ # Interestingly enough, not only calling methods, but also "repacking" values
+ # into variables work:
+ def repack(*, **)
+ x = * # this is syntax error
+ x = [*] # but this will work and put [1, 2] in x
+ x, y = [*] # and this will work and put 1 in x and 2 in y
+ z = {**} # this will put {a: :b} into z
+ end
+ repack(1, 2, a: :b)
+
+ # While the latter example might seem just a curiosity, it could help with
+ # quick debugging of path-through code.
+ # Imagine the `get` method above fails in one particular case.
+ # We can adjust it this way, temporarily:
+ def get(url, **)
+ binding.irb if {**}.dig(:headers, :content_type) == 'application/json'
+ send_request(:get, url, **)
+ end
+
+ # Parentheses are important for correct parsing.
+ # Imagine this:
+ def test(*)
+ # this, depending on further code, will be either a SyntaxError,
+ # or interpreted as call_something() * next_statement
+ call_something *
+ # ...
+ end
+
+ # Procs don't support anonymous arguments:
+ proc { |*| p(*) }
+ # Somewhat confusingly, this definitions raises:
+ # no anonymous rest parameter (SyntaxError)
+ # ...meaning surrounding method doesn't have them.
+
+ # And if it does, they would be used, not proc's arguments:
+ def test(*)
+ proc { |*| p(*) }.call(1)
+ end
+
+ test(2)
+ # prints 2 -- even inside proc, method's arguments are used for forwarding
+ ```
+* **Note:** Whether anonymous arguments should be supported in procs [is discussed](https://bugs.ruby-lang.org/issues/19370).
+
+### Constant assignment evaluation order changed
+
+For a long time, statements like `module_expression::CONST_NAME = value_expression` first evaluated `value_expression` and then `module_expression`. This was changed to calculate `module_expression` first.
+
+* **Reason:** Just making it consistent with other assignment expressions, which tend to calculate the left part before the right.
+* **Discussion:** [Bug #15928](https://bugs.ruby-lang.org/issues/15928)
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ # synthetic demonstrational example:
+ def make_a_class
+ puts "Making a class"
+ Class.new
+ end
+
+ make_a_class::CONST = 42.tap { puts "Calculating the value" }
+ # Prints:
+ # In Ruby 3.1:
+ # Calculating the value
+ # Making a class
+ # In Ruby 3.2:
+ # Making a class
+ # Calculating the value
+
+ # Even simpler:
+ NonExistentModule::CONST = 42.tap { puts "Calculating the value" }
+ # Ruby 3.1:
+ # Prints "Calculating the value"
+ # raises "uninitialized constant NonExistentModule" (NameError)
+ # Ruby 3.2:
+ # just raises "uninitialized constant NonExistentModule" (NameError)
+ ```
+* **Note:** The problem is rarely relevant, but might eventually manifest itself in complicated metaprogramming or autoloading. Or, like in the last example, some effectful value might be calculated before discovering there is nowhere to put it in. According to discussion on the tracker, the old behavior was never intentional, it was just too hard to fix.
+
+### Behavior of module reopening/redefinition with included modules changed
+
+When some module/class name is available at the top level context from the included modules, and a new class/module is defined, previously it was considered a reopening of existing module; since Ruby 3.2, it is a creation of a new module.
+
+* **Reason:** As one file's code has no control what is included in other files (may be non-obvious to code's author), cryptic behaviors might've emerged by treating included modules as reopenable on a top level.
+* **Discussion:** [Feature #18832](https://bugs.ruby-lang.org/issues/18832)
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ require 'net/http'
+
+ # ...might've happened in some of required files
+ include Net
+
+ p HTTP
+ #=> Net::HTTP -- from included Net module
+
+ # plan to define some of our app-specific HTTP services here
+ module HTTP
+ # ...
+ end
+ # Ruby 3.1: HTTP is not a module (TypeError)
+ # Because it assumes you reopening HTTP class from included Net
+ # The error is hard to understand and even harder to bypass
+ # Ruby 3.2: Successfully defines a new empty module, unrelated to Net::HTTP
+
+ p HTTP
+ #=> HTTP -- now it is a new module,
+ # and Net::HTTP is available only by fully-qualified name
+ ```
+
+### Keyword argument separation leftovers
+
+A few edge cases after [big keyword argument separation](3.0.html#keyword-arguments-are-now-fully-separated-from-positional-arguments) were fixed:
+
+* Erroneous autosplatting of positional arguments in procs. Discussion: [Bug #18633](https://bugs.ruby-lang.org/issues/18633)
+ ```ruby
+ test = proc { |arg, **keywords| p(arg:, keywords:) }
+ test.call(1, 2)
+ # Prints: {:arg=>1, :keywords=>{}}, 2 is lost as an extra argument, as expected
+ # But...
+ test.call([1, 2])
+ # Ruby 3.1: prints {:arg=>1, :keywords=>{}}, extra unexpected splatting & loss of 2s
+ # Ruby 3.2: prints {:arg=>[1, 2], :keywords=>{}}, as expected
+ ```
+* The methods that splat arguments were fixed to consistently treat keyword arguments according to `ruby2_keywords` tag. Discussion: [Bug #18625](https://bugs.ruby-lang.org/issues/18625), [Bug #16466](https://bugs.ruby-lang.org/issues/16466)
+ ```ruby
+ def method_with_keywords(**kw) = p kw
+
+ # This should never work: method is not marked with ruby2_keywords,
+ # so it shouldn't ever unpack positional arguments into keyword arguments.
+ def method_with_positional(*args) = method_with_keywords(*args)
+
+ method_with_positional(a: 1)
+ # Ruby 3.1 and 3.2: behaves as expected:
+ # wrong number of arguments (given 1, expected 0) (ArgumentError)
+
+ # This should work: method is marked with ruby2_keywords, so it
+ # is able to repack hash as keyword_args
+ ruby2_keywords def old_method(*args) = method_with_keywords(*args)
+
+ old_method(a: 1)
+ # Ruby 3.1 and 3.2: behaves as expected:
+ # prints {:a => 1}
+
+ # This shouldn't work, but erroneously worked in 3.1: after `bad_old_method`
+ # delegated the hash, it preserved "I am Ruby 2 keywords" through other
+ # methods (even if they aren't marked to be compatible).
+ ruby2_keywords def bad_old_method(*args) = method_with_positional(*args)
+
+ bad_old_method(a: 1)
+ # Ruby 3.1: prints {:a => 1}
+ # Ruby 3.2: wrong number of arguments (given 1, expected 0) (ArgumentError)
+ ```
+
+### Removals
+
+* Constants:
+ * `Fixnum` and `Bignum` (deprecated since unification into `Integer` in [2.4](/2.4.html#fixnum-and-bignum-are-unified-into-integer))
+ * `Random::DEFAULT` (deprecated in favor of per-Ractor random generator since [3.0](/3.0.html#randomdefault-behavior-change))
+* Methods:
+ * `Dir.exists?`, `File.exists?` (deprecated since 2.1 as a general rule of having "bare verb" as a method name)
+ * `Object#=~` (deprecated since [2.6](/2.6.html#minor-changes))
+ * `Object#taint`, `#untaint`, `#tainted?`, `#trust`, `#untrust`, `#untrusted?` (deprecated together with a general concept of "safety" since [2.7](/2.7.html#safe-and-taint-concepts-are-deprecated-in-general))
+
+
+## Core classes and modules
+
+### `Kernel#binding` raises if accessed not from Ruby
+
+`binding` is a method that returns "current context" ([Binding](https://docs.ruby-lang.org/en/3.2/Binding.html)) object, allowing access to local variables, `self`, evaluating code in that context, etc. The problem solved was that it was accessible from C methods, too, but as C methods call don't push new "execution frames," the binding returned was of the last calling Ruby method, which was useless and misleading. Since Ruby 3.2, the method raises an exceptions in such situations.
+
+* **Discussion:** [Bug #18487](https://bugs.ruby-lang.org/issues/18487)
+* **Documentation:** [Kernel#binding](https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-binding) (no mention for the behavior in non-Ruby frame)
+* **Code:** To demonstrate the practical implications, the C code should be written, but to get the gist of what's happening, we can do this:
+ ```ruby
+ # The callable binding object, that will "bind" itself to argument, and call it in the context
+ binding_caller = Kernel.instance_method(:binding).method(:bind_call)
+
+ binding_caller.call(nil).local_variables
+ # [:binding_caller] -- it is performed in the current context
+
+ # method just accepts block and just calls it
+ def test1(&)
+ local_val = 'test'
+ yield(nil)
+ end
+
+ test1(&binding_caller).local_variables
+ #=> [:local_val] -- we got the binding of test1, as expected
+
+ def test2(&)
+ local_val = 'test'
+ # Expectations: we pass block further, so it will be performed
+ # inside #map, and the binding was to "insides" of each argument
+ [1].map(&)
+ end
+
+ # Reality: #map is method defined in C, it doesn't has its own
+ # "context frame",
+ test2(&binding_caller).first.local_variables
+ # So, in Ruby 3.1:
+ # => [:local_val] -- we still received binding of the previous method
+ # in call chain
+ # In Ruby 3.2:
+ # Cannot create Binding object for non-Ruby caller (RuntimeError)
+ ```
+* **Note:** `TracePoint#binding` was also adjusted for C methods, see [below](#tracepointbinding-returns-nil-for-c_callc_return).
+
+### Class and Module
+
+#### `Class#attached_object`
+
+For singleton classes, returns the object this class is for; otherwise, raises `TypeError`.
+
+* **Reason:** The "what is this singleton class around of" is useful in metaprogramming, introspection and code analysis, especially when some class methods are added via `class << self`.
+* **Discussion:** [Feature #12084](https://bugs.ruby-lang.org/issues/12084)
+* **Documentation:** [Class#attached_object](https://docs.ruby-lang.org/en/3.2/Class.html#method-i-attached_object)
+* **Code:**
+ ```ruby
+ String.attached_object
+ # raises `String' is not a singleton class (TypeError)
+ "foo".singleton_class.attached_object
+ #=> "foo"
+
+ # or
+ class A
+ class << self
+ # here we are inside singleton class
+ p attached_object
+ #=> A
+ end
+ end
+
+ # Usage for advanced metaprogramming:
+
+ module MyCoolPlugin
+ def self.prepended(mod)
+ if mod.singleton_class? && mod.attached_object < Enumerable
+ mod.include MyCoolPlugin::ServicesForEnumerable
+ end
+ end
+ end
+
+ class Simple
+ class << self
+ # prepends only simple version of MyCoolPlugin
+ prepend MyCoolPlugin
+ end
+ end
+
+ class MyArray < Array
+ class << self
+ # Prepend MyCoolPlugin, and includes MyCoolPlugin::ServicesForEnumerable
+ prepend MyCoolPlugin
+ end
+ end
+
+ # Usage for documentation/code analysis purposes:
+
+ require 'active_support/all'
+ Time.zone #=> nil, defined by ActiveSupport
+
+ # Application-specific Time extensions
+ class MyTime < Time
+ end
+
+ m = MyTime.method(:zone)
+ # => #
+
+ # Now, if we want to path just this method to some
+ # documentation or introspection system, it has enough information
+ # to tell who it belongs to
+ m.owner
+ #=> #
+ m.receiver
+ #=> MyTime
+
+ # But before 3.2, there was no way to programmatically go from
+ # singleton class #, to the regular class Time, which
+ # the method is defined in, from the human point of view.
+
+ # Now, there is:
+ m.owner.attached_object
+ # => Time
+ # ...an our documentation/introspection system can properly describe
+ # it as a class method of Time.
+ ```
+* **Note:** The method will not work with "special" Ruby objects (`nil`, `true`, and `false`) which have their `singleton_class` implementations redefined to return regular class:
+ ```ruby
+ nil.singleton_class #=> NilClass, not #
+ class << nil
+ attached_object # raises `NilClass' is not a singleton class (TypeError)
+ end
+ ```
+
+#### `Module#const_added`
+
+A "hook" method, called after the constant was defined in a module.
+
+* **Reason:** The method was proposed as helpful for autoloader libraries (like `zeitwerk`), but it also can be useful in metaprogramming, like "store a registry of nested classes of a particular type." Or "validate that some parent constant is redefined in a particular way."
+* **Discussion:** [Feature #17881](https://bugs.ruby-lang.org/issues/17881)
+* **Documentation:** [Module#const_added](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-const_added)
+* **Code:**
+ ```ruby
+ module Test
+ def self.const_added(name)
+ puts "const_added: #{name} = #{const_get(name)}"
+ end
+
+ # The method is called AFTER the constant is actually
+ # defined, so its value is already available.
+ FOO = 1
+ # Prints:
+ # const_added: FOO = 1
+
+ # Each constant override triggers a method again:
+ FOO = 2
+ # Prints:
+ # const_added: FOO = 2
+
+ # Nested class definition:
+ class Nested
+ puts "Class definition body"
+ end
+ # Prints:
+ # const_added: Nested = Test::Nested
+ # Class definition body
+
+ # Note that const_added invoked at the BEGINNING of class being defined,
+ # not after its body is processed.
+ end
+ ```
+* **Note:** To understand the last example—why `const_added` was called before class definition is fully finished—you should consider that the class/module name becomes immediately known to Ruby after its definition is _opened_, allowing things like this:
+ ```ruby
+ module Test
+ class Nested
+ # outdated way of writing def self.class_method, but it works,
+ # because Nested is already known name here.
+ def Nested.class_method
+ end
+
+ # or this:
+ weird_class_method
+ # raises nice "undefined local variable or method `weird_class_method' for Test::Nested"
+ # ...because the name `Test::Nested` is already associated with current class
+ end
+ end
+ ```
+
+#### `Module#undefined_instance_methods`
+
+Lists methods that some module or class removed explicitly with `undef`.
+
+* **Reason:** _Honestly, I have no idea. The discussion ticket doesn't clarify this either! I assume it is just for completeness, to make everything that Module **can** do with method definitions to be accessible programmatically. Or, might help with debuggin of some evil/buggy code that does undefining of wrong methods. --zverok_
+* **Discussion:** [Feature #12655](https://bugs.ruby-lang.org/issues/12655)
+* **Documentation:** [Module#undefined_instance_methods](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-undefined_instance_methods)
+* **Code:**
+ ```ruby
+ class ImmutableArray < Array
+ undef :select!, :reject! #, ... etc
+ end
+
+ class UserArray < ImmutableArray
+ undef :map
+ end
+
+ ImmutableArray.undefined_instance_methods #=> [:select!, :reject!]
+ UserArray.undefined_instance_methods #=> [:map] -- only methods undefined by this module, not ancestors
+ ```
+
+### Refinements
+
+There are several new methods that improve the discovery and ability to debug for complicated code when it uses refinements. Those methods are improving answers to questions like "what methods are available in the current context and why?", "what methods will be available if I'll use that module" etc.
+
+#### `Module#refinements`
+
+Returns list of refinements the module defines.
+
+* **Discussion:** [Feature #12737](https://bugs.ruby-lang.org/issues/12737)
+* **Documentation:** [Module#refinements](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-refinements)
+* **Code:**
+ ```ruby
+ module MathShortcuts
+ refine Numeric do
+ def sqrt = Math.sqrt(self)
+ # ...
+ end
+
+ refine String do
+ def calculate(binding) = "#{self} = #{eval(self, binding)}"
+ end
+ end
+
+ module NoRefinements
+ end
+
+ MathShortcuts.refinements #=> [#, #]
+ NoRefinemens.refinements #=> []
+
+ # Introspection: what would this refinement refine?..
+ # The method also added in 3.2, see below
+ MathShortcuts.refinements[0].refined_class #=> Numeric
+ # Introspection: what methods would it add?
+ # (false means "don't include methods defined in ancestors")
+ MathShortcuts.refinements[0].instance_methods(false) #=> [:sqrt]
+ ```
+
+#### `Refinement#refined_class`
+
+* **Discussion:** [Feature #12737](https://bugs.ruby-lang.org/issues/12737)
+* **Documentation:** [Refinement#refined_class](https://docs.ruby-lang.org/en/3.2/Refinement.html#method-i-refined_class)
+* **Code:** See example above that demonstrates usage of `#refined_class` together with `Module#refinements`.
+* **Note:** The name of the method implies only classes can be refined, which is not true; modules can be refined also, and `refined_class` will promptly return them:
+ ```ruby
+ module BetterEnum
+ refine Enumerable do
+ def each2(&block) = each_slice(2, &block)
+ end
+ end
+
+ BetterEnum.refinements[0].refined_class #=> Enumerable
+
+ using BetterEnum
+ (1..6).each2.to_a #=> [[1, 2], [3, 4], [5, 6]]
+ ```
+ Currently [discussed](https://bugs.ruby-lang.org/issues/19366).
+
+#### `Module.used_refinements`
+
+Returns instances of `Refinement` used in the current context.
+
+* **Discussion:** [Feature #14332](https://bugs.ruby-lang.org/issues/14332)
+* **Documentation:** [Module.used_refinements](https://docs.ruby-lang.org/en/3.2/Module.html#method-c-used_refinements)
+* **Code:**
+ ```ruby
+ # See MathShortcuts module definition above
+
+ class Calculator
+ using MathShortcuts
+
+ # Works inside refined module...
+ p Module.used_refinements #=> [#, #]
+
+ def hypotenuse(c1, c2)
+ # ...and inside its methods
+ p Module.used_refinements #=> [#, #]
+
+ "(c1**2 + c2**2).sqrt".calculate(binding)
+ end
+ end
+
+ # Use method with refinements, triggering all the debug print
+ puts Calculator.new.hypotenuse(5, 6) #=> (c1**2 + c2**2).sqrt = 7.810249675906654
+
+ # Outside of refined class, refinements are empty
+ p Module.used_refinements #=> []
+ ```
+* **Notes:**
+ * Note that `used_refinements` is a _class_ method of a `Module`, and put there just for organizational purposes, while returning refinements list of the _current context_. There is no way to ask arbitrary module which refinements it uses (e.g., there is no `Calculator.used_refiments`).
+ * Just as a point of interest, the method with this name was proposed short after introducing of concept of refinements and was [discussed](https://bugs.ruby-lang.org/issues/7418) even before 2.0 release. It eventually became [Module.used_modules](https://docs.ruby-lang.org/en/3.2/Module.html#method-c-used_modules) introduced [in 2.4](2.4.html#moduleused_modules): that method just returned a list of modules with refinements, enabled in the current scope via `using`. The result of this method is not very fine-grained (as one refining method can refine _many_ objects at once, and it is impossible to inspect which exactly and what methods were added). After introduction of the `Refinement` class [in 3.1](3.1.html#refinement-class) it became reasonable to give easier access to particular refinements available in the current context.
+
+### Integer#ceildiv
+
+The integer division that always rounds up.
+
+* **Reason:** There are many simple use cases like pagination (when "21 items / 10 per page" should yield "3 pages"). It seems that the method is a direct equivalent of `a.fdiv(b).ceil`, and as such, annoyingly unnecessary, but `fdiv`, due to floating point imprecision, might produce surprising results in edge cases:
+ ```ruby
+ 99999999999999999.fdiv(1).ceil
+ # => 100000000000000000
+ 99999999999999999.ceildiv(1)
+ # => 99999999999999999
+ ```
+* **Discussion:** [Feature #18809](https://bugs.ruby-lang.org/issues/18809)
+* **Documentation:** [Integer#ceildiv](https://docs.ruby-lang.org/en/3.2/Integer.html#method-i-ceildiv)
+* **Code:**
+ ```ruby
+ 9.ceildiv(3) #=> 3
+ 10.ceildiv(3) #=> 4
+ -10.ceildiv(3) #=> -3 -- always rounds up, regardless of the sign
+ # If the divisor is not integer, the result is equivalent to dividing by divisor.round
+ 10.ceildiv(2.1) #=> 5 -- like 10.ceildiv(2)
+ 10.ceildiv(2.6) #=> 4 -- like 10.ceildiv(3)
+ ```
+* **Note:** Unlike most of other operations, `#ceildiv` ignores [numeric coercion protocols](https://docs.ruby-lang.org/en/3.2/Numeric.html#method-i-coerce):
+ ```ruby
+ class StringNumber
+ def initialize(val) = @val = val.to_s
+ def coerce(other) = [other, @val.to_i]
+ end
+
+ 10 / StringNumber.new('3') #=> 3, argument is first converted with #coerce if possible
+ 10.fdiv StringNumber.new('3') #=> 3.3333333333333335, same
+ 10.ceildiv StringNumber.new('3') # ArgumentError
+ ```
+ It is already [fixed in the current master branch](https://github.com/ruby/ruby/pull/7118) and will behave as expected at Ruby 3.2.1
+
+### Strings and regexps
+
+#### Byte-oriented methods
+
+Several method were added that operate on multibyte strings at byte-offset level, regardless of the encoding.
+
+* **Reason:** Low-level processing of strings (like networks middleware, or efficient search algorithms, or packing/unpacking) might need an ability to operate on a level of single bytes, regardless of original string's encoding. It is especially important while handling variable-length encodings like UTF-8. Before methods introduction, the only way to perform byte-level processing was to forcing string encoding into `ASCII-8BIT`, process, and then force encoding back.
+* **Discussion:** [Feature #13110](https://bugs.ruby-lang.org/issues/13110) (`String#byteindex`, `String#byterindex`, `MatchData#byteoffset`), [Feature #18598](https://bugs.ruby-lang.org/issues/18598) (`String#bytesplice`)
+* **Documentation:** [String#byteindex](https://docs.ruby-lang.org/en/3.2/String.html#method-i-byteindex), [String#byterindex](https://docs.ruby-lang.org/en/3.2/String.html#method-i-byterindex), [String#bytesplice](https://docs.ruby-lang.org/en/3.2/String.html#method-i-bytesplice), [MatchData#byteoffset](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-byteoffset)
+* **Code:**
+ ```ruby
+ str = 'Слава Україні'
+
+ str.index('а') #=> 2, character index
+ str.byteindex('а') #=> 4, byte index, because Cyrilic letters in UTF-8 take 2 bytes each
+
+ str.rindex('а') #=> 9: character index of the last entrance of character 'а'
+ str.byterindex('а') #=> 17: byte index
+
+ match = str.match(/Слава\s+(?.+)/)
+ match.offset(1) #=> [6, 13]
+ match.byteoffset(1) #=> [11, 25]
+ match.offset(:name) #=> [6, 13]
+ match.byteoffset(:name) #=> [11, 25]
+
+ str = 'війна'
+ str.bytesplice(2..5, '...') #=> "..." -- returns replacement string
+ str #=> "в...на" -- original string's bytes 2-3, 4-5 (e.g. chars 1 and 2) are replaced
+
+ # Unlike byteslice getter, bytesplice setter checks character boundaries:
+ str = 'війна'
+ str.byteslice(1..3) #=> "\xB2і" -- works, even if the slice is mid-character
+ str.bytesplice(1..3, '...') # offset 1 does not land on character boundary (IndexError)
+ ```
+* **Note:** After 3.2 release, `bytesplice` behavior [had changed](https://bugs.ruby-lang.org/issues/19314#note-8) to return `self` instead of replacement string.
+
+#### `String#dedup` as an alias for `-"string"`
+
+The method produces frozen and deduplicated string without changing the receiver.
+
+* **Reason:** Since [Ruby 2.5](2.5.html#string--optimized-for-memory-preserving), `-"string"` produces a frozen and **deduplicated** copy: all instances with the same content take the same place in memory. But it is a less-known fact, that is also hard to guess from the code and quick look into the docs. At the same time, it became a useful idiom for reducing a memory footprint of long-running applications. The `#dedup` alias is focused on the behavior, and also more chainable than unary `-`.
+* **Discussion:** [Feature #18595](https://bugs.ruby-lang.org/issues/18595)
+* **Documentation:** [String#dedup](https://docs.ruby-lang.org/en/3.2/String.html#method-i-dedup)
+* **Code:**
+ ```ruby
+ protocols = %w[http https]
+ domains = %w[company.com api.company.com]
+
+ # if in various places of the program we constructing the same URLs many times...
+ # ...there might be many similar strings sitting everywhere and taking memory
+ urls = 100.times.map { protocols.sample + '://' + domains.sample }
+ urls.uniq.count
+ # => 4 -- we store 4 same strings again and again
+ urls.map(&:object_id).uniq.count
+ # => 100 -- but it is 100 different objects
+
+ urls.map!(&:dedup)
+ urls.map(&:object_id).uniq.count
+ # => 4
+
+ # The `map(&:dedup)` above could previously been written as
+ urls.map!(&:-@)
+ # ...which calls unary minus on arguments
+ # But it is both uglier, and shows the intention worse.
+ ```
+
+#### `Regexp.new`: passing flags as a string is supported
+
+* **Reason:** most of those working with regexps are used to short flag names like `/foo/i` or `/bar/m`. At the same time, when `Regexp.new` is constructed dynamically, there was necessary to use numeric flags `Regexp::IGNORECASE | Regexp::MULTILINE`. They are are more formal (and can be thought as more obvious), but string ones are those most of the Rubyists remember.
+* **Discussion:** [Feature #18788](https://bugs.ruby-lang.org/issues/18788)
+* **Documentation:** [Regexp.new](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-new) (`options` argument)
+* **Code:**
+ ```ruby
+ Regexp.new('username', 'i') #=> /username/i
+ # All known options work:
+ Regexp.new(<<~'HTML', 'imx')
+ <(\w+) .*?>
+ [^<]+
+ \1>
+ HTML
+ #=> same as
+ # %r{<(\w+) .*?>
+ # [^<]+
+ # \1>}imx
+
+ # Unknown option raises
+ Regexp.new('foo', 'g') # unknown regexp option: g (ArgumentError)
+ ```
+* **Notes:** One quirk that might be surprising with a wrong use of the new feature is that `Regexp.new` treats any truthy value of unrecognized type as "ignore case". So,
+ ```ruby
+ # This might erroneously thought to "work":
+ Regexp.new('foo', %w[i]) #=> /foo/i, looks like array of options is also acceptable?..
+ # ...but actually it is that any truthy value is treated as "ignorecase = true":
+ Regexp.new('foo', %w[abc]) #=> /foo/i
+ ```
+
+#### `Regexp`: ReDoS vulnerability prevention
+
+* **Reason:** The [ReDoS attack](https://en.wikipedia.org/wiki/ReDoS) is overloading the system by providing malformed regexp or string to match. The possibility for this attack is mostly theoretical, but still reported as a security vulnerability in some contexts. New Ruby version introduces several features that might mitigate the attack (or, at least, a vulnerability report):
+ * Setting explicit timeout for Regexp execution;
+ * `Regexp.linear_time?` analysis method;
+ * _(CRuby-specific) Cache-based optimization: many Regexps now perform in linear time even on very long strings (at the cost of increased memory consumption);_
+* **Discussion:** [Feature #17837](https://bugs.ruby-lang.org/issues/17837) (timeout), [Feature #19104](https://bugs.ruby-lang.org/issues/19104) (cache-based optimization), [Feature #19194](https://bugs.ruby-lang.org/issues/19194) (`.linear_time?`)
+* **Documentation:** [Regexp.timeout](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-timeout), [Regexp.timeout=](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-timeout-3D), [Regexp.new](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-new) (`timeout:` keyword argument), [Regexp.linear_time?](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-linear_time-3F)
+* **Code:**
+ ```ruby
+ Regexp.linear_time?(/a+$/) #=> true
+ Regexp.linear_time?(/(a+)\1*$/) #=> false, backtracking is complicated
+
+ Regexp.timeout = 0.005
+ # Just a demo: simple yet very ambigous regexp applied to very large string
+ /(a+)\1*$/.match?('a' * 1_000_000)
+ # Depending on your machine's performance, might raise:
+ # `match?': regexp match timeout (Regexp::TimeoutError)
+
+ # When applied to a smaller string
+ /(a+)\1*$/.match?('a' * 1_000)
+ #=> true
+
+ # This works, too:
+ Regexp.new(/(a+)*$/, timeout: 0.005).match?('a' * 1_000_000)
+ # Might raise:
+ # `match?': regexp match timeout (Regexp::TimeoutError)
+ ```
+* **Note:** While `Regexp.linear_time?` is part of the official language API, its results for the same regexps might change between versions and implementations.
+
+### `Time.new` can parse a string
+
+The new protocol for `Time.new` is introduced, that parses Time from string.
+
+* **Reason:** Before Ruby 3.2, there core class `Time` provided no way to to get back a `Time` value from any serialization, including even simple `Time#inspect` or `#to_s`. The `Time.parse` provided by standard library `time` (not core functionality, doesn't work with explicit `require 'time'`), and tries to parse every imaginable format, while `Time.new` with string is stricter.
+* **Discussion:** [Feature #18033](https://bugs.ruby-lang.org/issues/18033)
+* **Documentation:** [Time.new](https://docs.ruby-lang.org/en/3.2/Time.html#method-c-new)
+* **Code:**
+ ```ruby
+ Time.new('2023-01-29 00:29:30')
+ # => 2023-01-29 00:29:30 +0200
+
+ # Desired timezone can be provided as part of a string:
+ Time.new('2023-01-29 00:29:30 +08:00')
+ #=> 2023-01-29 00:29:30 +0800
+ # ...or like with other .new protocols, as a separate in: argument:
+ Time.new('2023-01-29 00:29:30', in: '+08:00')
+ #=> 2023-01-29 00:29:30 +0800
+
+ # The accepted format is much stricter than Time.parse:
+ require 'time'
+ Time.parse('Jan 29, 2023')
+ #=> 2023-01-29 00:00:00 +0200
+ Time.new('Jan 29, 2023')
+ # in `initialize': can't parse: "Jan 29, 2023" (ArgumentError)
+
+ # Even incomplete time is considered an error (but see Notes below):
+ Time.new('2023-01-29 00:29')
+ # in `initialize': missing sec part: 00:29 (ArgumentError)
+ ```
+* **Notes:**
+ * A few improvements are planned to be made to the parser strictness and robustness in 3.2.1 (see [Bug #19296](https://bugs.ruby-lang.org/issues/19296), [Bug #19293](https://bugs.ruby-lang.org/issues/19293)), for example:
+ ```ruby
+ # This works, but is considered a bug, the method should allow
+ # only fully-specified time
+ Time.new("2023-01-29")
+ #=> 2023-01-29 00:00:00 +0200
+ ```
+ * `Time.new('2023')` works, too, but it is a feature that worked before (force-conversion of singular year argument to integer), see [Bug #19293](https://bugs.ruby-lang.org/issues/19293). It will probably be deprecated, but can't be quickly removed due to backward compatibility.
+
+### `Struct` and `Data`
+
+#### `Struct` can be initialized by keyword arguments by default
+
+The default behavior of `Struct` since 3.2 is to accept both positional and keyword arguments in constructor.
+
+* **Reason:** Since introduction of `Struct.new(, keyword_init: true)` in 2.5, it was frequently criticized as clumsy
+* **Discussion:** [Feature #16806](https://bugs.ruby-lang.org/issues/16806)
+* **Documentation:** [Struct.new](https://docs.ruby-lang.org/en/3.2/Struct.html#method-c-new)
+* **Code:**
+ ```ruby
+ User = Struct.new(:id, :name)
+ # This works:
+ User.new(1, 'Joan') #=> #
+ # Since 3.2, this works too:
+ User.new(id: 1, name: 'Joan') #=> #
+
+ # keyword_arguments: true/false still can be provided to make the behavior stricter:
+
+ User = Struct.new(:id, :name, keyword_init: true)
+ User.new(id: 1, name: 'Joan') #=> #
+ User.new(1, 'Joan')
+ # in `initialize': wrong number of arguments (given 2, expected 0) (ArgumentError)
+
+ User = Struct.new(:id, :name, keyword_init: false)
+ User.new(1, 'Joan') #=> #
+ User.new(id: 1, name: 'Joan')
+ # => #1, :name=>"Joan"}, name=nil>
+ # Note it is not ArgumentError, but interpreting all keyword args as one positional hash
+ ```
+* **Notes:**
+ * The incompatibility might be introduced by code that expected singular hash as an argument for a Struct initialization:
+ ```ruby
+ Wrapper = Struct.new(:json_data)
+ Wrapper.new(user: {name: 'Joan'})
+ # Ruby 3.0: works
+ # #{:name=>"Joan"}}>
+ # Ruby 3.1: warns, yet works
+ # warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).
+ # #{:name=>"Joan"}}>
+ # Ruby 3.2: breaks
+ # in `initialize': unknown keywords: user (ArgumentError)
+
+ # Fixed by explicitly setting `keyword_init: false` in struct definition:
+ Wrapper = Struct.new(:json_data, keyword_init: false)
+ Wrapper.new(user: {name: 'Joan'})
+ # => #{:name=>"Joan"}}>
+ # ...on Ruby 2.5-3.2, without any warnings
+
+ # or, alternatively, as always wrapping hashes in {} explicitly,
+ # as 3.1's warning suggested:
+ Wrapper.new({user: {name: 'Joan'}})
+ # => #{:name=>"Joan"}}>
+ ```
+ * While the new behavior is convenient, one should be especially careful when redefining `#initialize` for `Struct`s to not break it:
+ ```ruby
+ User = Struct.new(:id, :new) do
+ # suppose we want to convert id to Integer before initializing.
+ # Note that it could be in `args.first`, or in `kwargs[:id]` now, so it is either this:
+ def initialize(*args, **kwargs)
+ if !args.empty?
+ args[0] = args[0].to_i
+ elsif kwargs.key?(:id)
+ kwargs[:id] = kwargs[:id].to_i
+ end
+ super(*args, **kwargs)
+ end
+
+ # or just post-processing...
+ def initialize(...)
+ super(...)
+ self.id = self.id.to_i
+ end
+ end
+ ```
+
+#### `Data`: new immutable value object class
+
+A new class for containing value objects: it is somewhat similar to `Struct` (and reuses some of the implementation internally), but is intended to be immutable, and have more modern and cleaner API.
+
+* **Reason:** Before 3.2, `Struct` was an ubiquitous data holder class in Ruby, but being designed a long time ago, it has its drawbacks, making it not suitable for all situations: it is mutable by design (have argument setters), and have APIs of both "value-alike" and "container-alike" types. But there is a lot of code using `Struct` in various ways (and for good reasons), so it can't be just redesigned. Several approaches was considered (including adding a "configure" API to `Struct`, allowing to specify "should it be mutable, should it be iterable, should it be hash-alike"), but in the end, a new class with smaller and stricter API was designed.
+* **Discussion:** [Feature #16122](https://bugs.ruby-lang.org/issues/16122)
+* **Documentation:** [Data](https://docs.ruby-lang.org/en/3.2/Data.html)
+* **Code:** Data is completely new, well-documented class. So we wouldn't try to demonstrate all details of its behavior, just give a brief overview.
+ ```ruby
+ Point = Data.define(:x, :y)
+
+ # Both positional and keyword arguments can be used
+ p1 = Point.new(1, 0) #=> #
+ p2 = Point.new(x: 0, y: 1) #=> #
+
+ # all arguments are mandatory
+ Point.new(1) # missing keyword: :y (ArgumentError)
+
+ # #initialize might be redefined to provide default arguments or argument conversions
+ Point3D = Data.define(:x, :y, :z) do
+ def initialize(x:, y:, z: 0) = super
+ end
+
+ Point3D.new(x: 1, y: 2)
+ # => #
+
+ # the redefinition above is enough to handle keyword AND position arguments:
+ Point3D.new(1, 2)
+ # => #
+
+ # there is no setters or any other way to change already created object
+ p1.x = 5 # undefined method `x=' for # (NoMethodError)
+ p1.instance_variable_set('@z', 100) # can't modify frozen Point: # (FrozenError)
+
+ # #with method can be used to construct new instances,
+ # replacing only parts of the data:
+ p1.with(y: 100) #=> #
+ ```
+* **Notes:**
+ * The class with the same name (`Data`) [existed before](https://docs.ruby-lang.org/en/2.5.0/Data.html) for internal purposes—as a recommended empty base class for classes defined in C extensions. It was deprecated [since Ruby 2.5](2.5.html#misc), and removed in Ruby 3.0.
+ * On `Data` immutability: note that only the `Data`-derived object itself is frozen, but there is no deep freezing of instance variables. So this is still possible (and up to user code to prevent, if undesirable):
+ ```ruby
+ Result = Data.define(:array)
+ res = Result.new([1, 2, 3])
+ res.instance_variable_set('@size', 3) #=> can't modify frozen Result, as expected
+ # but...
+ res.array << 4 # works
+ res
+ #=> #
+
+ # Can shoot yourself in the foot in code doing something like...
+ case res
+ in Result(array:) # unpack into local variable
+ array.reverse! # process it inplace, considering it independent local variable...
+ # ...pass processed somewhere else...
+ end
+
+ # ...but actually data WAS changed:
+ res
+ #=> #
+ ```
+ * `#with` method in Ruby 3.2 is naive and just copies all old and new attributes to the new instance, without invoking any custom initialization methods. In the next version, though, it [expected to call `#initialize`](https://bugs.ruby-lang.org/issues/19259):
+ ```ruby
+ Point = Data.define(:x, :y) do
+ def initialize(x:, y:) = super(x: x.to_i, y: y.to_i)
+ end
+
+ p = Point.new('1', '2')
+ # => # -- conversion performed through #initialize
+ p.with(y: '3')
+ # => # -- #initialize is bypassed
+
+ # Probably since Ruby 3.2.1:
+ p.with(y: '3')
+ # => #
+ ```
+
+### Pattern matching
+
+* "Find pattern" `value in [*, pattern, *]` is no longer experimental. [Feature #18585](https://bugs.ruby-lang.org/issues/18585)
+
+#### `MatchData`: added `#deconstruct` and `#deconstruct_keys`
+
+As a part of the effort to make core classes more pattern matching friendly, `MatchData` (the result of regexp matching) now can be deconstructed.
+
+* **Discussion:** [Feature #18821](https://bugs.ruby-lang.org/issues/18821)
+* **Documentation:** [MatchData#deconstruct](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct), [MatchData#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct_keys)
+* **Code:**
+ ```ruby
+ case connection_string.match(%r{postgres://(\w+):(\w+)@(.+)})
+ in 'admin', password, server
+ # do connection with admin rights
+ in ^DEV_USERS, _, 'dev-server.local'
+ # connect to dev server with any password
+ in user, password, server
+ # do regular connection
+ end
+
+ # Might be used just for quick and expressive unpacking of match results
+ connection_string = 'postgres://admin:secret@foo.amazonaws.com'
+ connection_string.match(%r{postgres://(\w+):(\w+)@(.+)}) => user, password, server
+ user #=> "admin"
+ password #=> "secret"
+ server #=> "foo.amazonaws.com"
+
+ # When named capture group is used, MatchData also provides hash unpacking:
+ connection_string.match(%r{postgres://(?\w+):(?\w+)@(?.+)}) => user:, password:, server:
+ user #=> "admin"
+ password #=> "secret"
+ server #=> "foo.amazonaws.com"
+```
+
+#### `Time#deconstruct_keys`
+
+`Time` now can be used in pattern matching too.
+
+* **Discussion:** [Feature #19071](https://bugs.ruby-lang.org/issues/19071)
+* **Documentation:** [Time#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Time.html#method-i-deconstruct_keys)
+* **Code:**
+ ```ruby
+ # `deconstruct_keys(nil)` shows all available keys:
+ Time.now.deconstruct_keys(nil)
+ # => {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0, :hour=>17, :min=>5, :sec=>56, :subsec=>(148452241/200000000), :dst=>false, :zone=>"EET"}
+
+ # Usage in pattern-matching:
+ case timestamp
+ in year: ...2022
+ puts "Far past!"
+ in year: 2022, month: 1..3
+ puts "Last year's first quarter"
+ in year: 2023, month:, day:
+ puts "#{day} of #{month}th month!"
+ # ...
+ end
+
+ # Check if it is the first Thursday of the current month:
+ if Time.now in wday: 4, day: ..7
+ # ...
+ ```
+
+* **Notes:**
+ * It was decided that `#deconstruct` method for `Time` doesn't make much sense, because the reasonable order for **all** of the time components is hard to define.
+ * Standard library classes `Date` and `DateTime` also receive similar implementations ([Date#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Date.html#method-i-deconstruct_keys), [DateTime#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/DateTime.html#method-i-deconstruct_keys)):
+ ```ruby
+ require 'date'
+
+ Date.today.deconstruct_keys(nil)
+ #=> {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0}
+ DateTime.now.deconstruct_keys(nil)
+ # => {:year=>2023, :month=>1, :day=>15, :yday=>15, :wday=>0, :hour=>17, :min=>19, :sec=>15, :sec_fraction=>(478525469/500000000), :zone=>"+02:00"}
+ ```
+
+### Enumerables and collections
+
+#### `Enumerator.product`
+
+Generates an enumerator from several other, yielding all possible combinations of their elements.
+
+* **Discussion:** [Feature #18685](https://bugs.ruby-lang.org/issues/18685)
+* **Documentation:** [Enumerator.product](https://docs.ruby-lang.org/en/3.2/Enumerator.html#method-c-product), [Enumerator::Product](https://docs.ruby-lang.org/en/3.2/Enumerator/Product.html)
+* **Code:**
+ ```ruby
+ enumerator = Enumerator.product(1.., %w[test me])
+ # => #
+
+ enumerator.take(6)
+ # => [[1, "test"], [1, "me"], [2, "test"], [2, "me"], [3, "test"], [3, "me"]]
+
+ # The arguments can be any object responding to `each_entry`,
+ # not necessary enumerator/enumerable
+ class ThreeBears
+ def each_entry
+ yield 'Papa Bear'
+ yield 'Mama Bear'
+ yield 'Little Bear'
+ end
+ end
+ Enumerator.product([1, 2], ThreeBears.new).to_a
+ # => [[1, "Papa Bear"], [1, "Mama Bear"], [1, "Little Bear"],
+ # [2, "Papa Bear"], [2, "Mama Bear"], [2, "Little Bear"]]
+ ```
+* **Notes:**
+ * It is [currently discussed](https://bugs.ruby-lang.org/issues/19324) that protocol for `Enumerator.product` is unlike `Array#product` (which is a method of the first argument of the expression).
+ * If one of the enumerators is effectful (can be iterated through only once), the current implementation would exhaust it on the first go:
+ ```ruby
+ require 'stringio'
+
+ # This will work as expected
+ io = StringIO.new('abc')
+ Enumerator.product(io.each_char, [1, 2, 3]).to_a
+ # => [["a", 1], ["a", 2], ["a", 3], ["b", 1], ["b", 2], ["b", 3], ["c", 1], ["c", 2], ["c", 3]]
+
+ # But this will produce less data than the full cross-product
+ # This will work as expected
+ io = StringIO.new('abc')
+ Enumerator.product([1, 2, 3], io.each_char).to_a
+ #=> [[1, "a"], [1, "b"], [1, "c"]]
+ ```
+ This is probably a [bug](https://bugs.ruby-lang.org/issues/19294).
+
+#### `Hash#shift` always returns `nil` if the hash is empty
+
+There was a bug/inconsistency with returning the default value if it is defined.
+
+* **Discussion:** [Bug #16908](https://bugs.ruby-lang.org/issues/16908)
+* **Documentation:** [Hash#shift](https://docs.ruby-lang.org/en/3.2/Hash.html#method-i-shift)
+* **Code:**
+ ```ruby
+ h = {a: 1}
+ h.shift #=> [:a, 1]
+ h.shift #=> nil, as expected
+ # but if the default for hash is defined...
+ h.default = :foo
+ h.shift
+ # 3.1: => :foo -- hard to explain, it isn't even [key, value] pair
+ # 3.2: => nil
+ ```
+
+#### `Set` became a built-in class
+
+Previously a part of standard library, Set (a collection of unique elements) was promoted to core class. No more need to `require 'set'` to use the class.
+
+* **Discussion:** [Feature #16989](https://bugs.ruby-lang.org/issues/16989)
+* **Documentation:** [Set](https://docs.ruby-lang.org/en/3.2/Set.html) (still mentions `require 'set'`, though)
+* **Notes:** As of 3.2, the only change is making the library auto-required without changing the implementation. `Set` is still not as integrated in Ruby as other collections, like `Hash` and `Array`: `Set` is implemented in Ruby, uses `Hash` as its internal storage (by creating a hash of `set_element => true` pairs), and doesn't have its own literal. There are distant plans to improve it, but with no particular schedule.
+
+#### `Thread::Queue`: timeouts for `pop` and `push`
+
+`timeout: ` parameter was added to methods `Queue#pop`, `SizedQueue#push`, `SizedQueue#pop`.
+
+* **Reason:** As thread queue is meant as a method of inter-thread communication, it is useful to provide a way for not hung a thread forever while waiting for input from other thread (or waiting for place in queue in case of `SizedQueue#push`)
+* **Discussion:** [Feature #18774](https://bugs.ruby-lang.org/issues/18774), [Feature #18944](https://bugs.ruby-lang.org/issues/18944)
+* **Documentation:** [Thread::Queue#pop](https://docs.ruby-lang.org/en/3.2/Thread/Queue.html#method-i-pop), [Thread::SizedQueue#pop](https://docs.ruby-lang.org/en/3.2/Thread/SizedQueue.html#method-i-pop), [Thread::SizedQueue#push](https://docs.ruby-lang.org/en/3.2/Thread/SizedQueue.html#method-i-push)
+* **Code:**
+ ```ruby
+ queue = Thread::Queue.new
+ sender = Thread.new do
+ queue.push(1)
+ queue.push(2)
+ end
+
+ # Expects 3 values from sender
+ receiver = Thread.new do
+ # This will print 1, 2, and then make receiver sleep forever:
+ # 3.times.each { p queue.pop }
+
+ # But this prints 1, 2, waits for 0.5 seconds and then prints `nil`
+ 3.times.map { p queue.pop(timeout: 0.5) }
+ end
+ [sender, receiver].each(&:join)
+
+ sized = Thread::SizedQueue.new(2)
+ sized.push(1, timeout: 0.5) #=> success, returns the queue object
+ sized.push(2, timeout: 0.5) #=> success, returns the queue object
+ sized.push(3, timeout: 0.5) #=> waits 0.5 seconds, returns nil
+ sized.size #=> 2, only 1 and 2 were pushed successfully
+ ```
+
+### Procs and methods
+
+#### `Proc#dup` returns an instance of subclass
+
+* **Reason:** Just for consistency with other core classes behavior.
+* **Discussion:** [Bug #17545](https://bugs.ruby-lang.org/issues/17545)
+* **Documentation:** —
+* **Code:**
+ ```ruby
+ class MyProc < Proc
+ # some additional custom methods...
+ end
+
+ MyProc.new { }.dup
+ # 3.1: => #
+ # 3.2: => #
+ ```
+* **Notes:**
+ * In general, inheriting from core classes is a questionable practice, and you probably should avoid it;
+ * Despite producing an instance of a subclass now, `#dup` doesn't call `#initialize_dup` constructor, so no custom data that you've associated with a subclass instance can't be preserved:
+ ```ruby
+ class TaggedProc < Proc
+ attr_reader :tag
+
+ def initialize(tag, &block)
+ @tag = tag
+ super(&block)
+ end
+
+ def initialize_dup(other) # this will NOT be invoked
+ @tag = other.tag
+ super
+ end
+ end
+
+ t = TaggedProc.new('test') { }
+ t.tag #=> 'test'
+ t.dup.tag #=> nil
+ ```
+ This is a [bug](https://bugs.ruby-lang.org/issues/19362) and will probably [change](https://github.com/ruby/ruby/pull/7178) in Ruby 3.2.1.
+
+#### `Proc#parameters`: new keyword argument `lambda: true/false`
+
+`parameters(lambda: true)` returns Proc parameters description _as if the proc was lambda_ (e.g. the parameters without defaults was mandatory), regardless of Proc's real "lambdiness."
+
+* **Reason:** The regular (non-lamba) proc always reports its positional arguments is optional. It corresponds to its behavior, but loses the information which of them have default values defined. It might be inconvenient when using procs in metaprogramming, like building wrapper objects, or defining methods based on procs.
+* **Discussion:** [Feature #15357](https://bugs.ruby-lang.org/issues/15357)
+* **Documentation:** [Proc#parameters](https://docs.ruby-lang.org/en/3.2/Proc.html#method-i-parameters)
+* **Code:**
+ ```ruby
+ prc = proc { |x, y=0| p(x:, y:) }
+ prc.parameters
+ # => [[:opt, :x], [:opt, :y]] -- for proc, all parameters are optional
+ # Whih corresponds to how it actually behaves: all params can be skipped:
+ prc.call
+ #=> {:x=>nil, :y=>0}
+
+ prc.parameters(lambda: true)
+ # => [[:req, :x], [:opt, :y]] -- in stricter lambda protocol, first parameter is required
+ # Which corresponds to how the corresponding lambda would treat
+ # its parameters:
+ lambda { |x, y=0| p(x:, y:) }.call
+ # wrong number of arguments (given 0, expected 1..2) (ArgumentError)
+
+ # The `lambda: false` call works, too, although arguably less useful:
+ l = ->(x, y=0) { }
+ l.parameters
+ # => [[:req, :x], [:opt, :y]]
+ l.parameters(lambda: false)
+ # => [[:opt, :x], [:opt, :y]]
+ ```
+
+#### `Method#public?`, `#protected?`, and `#private?` are removed
+
+Predicates to check method visibility added in Ruby 3.1 were reverted.
+
+* **Reason:** The new feature implementation have led to several bugs with `Method` class behavior; while investigating the root cause for those bugs, Matz have decided that method's visibility is not its inherent property, but rather a property of the module/object that owns the method, and as such, is already present in form of `Module#{private,public,protected}_instance_methods` and `Object#{private,public,protected}_methods`
+* **Discussion:** [Feature #11689#note-24](https://bugs.ruby-lang.org/issues/11689#note-24)
+* **Notes:** The discussion whether the feature should be un-reverted is still ongoing!
+
+#### `UnboundMethod`: more consistent reporting on what module it belongs to
+
+Since 3.2, `UnboundMethod`'s `#inspect` and comparison with other `UnboundMethod` only considers the module it defined in, not the actual module it was unbound from.
+
+* **Reason:** The change just aligns auxiliary methods with the main `UnboundMethod` implementation. No usage of unbound method is affected by what was the original class or object it was unbound from, only by the place of definition.
+* **Discussion:** [Feature #18798](https://bugs.ruby-lang.org/issues/18798)
+* **Affected methods:** [UnboundMethod#==](https://docs.ruby-lang.org/en/3.2/UnboundMethod.html#method-i-3D-3D), `#inspect` (documentation not updated)
+* **Code:**
+ ```ruby
+ tally = Array.instance_method(:tally)
+ p tally
+ # 3.1: => #
+ # 3.2: => #
+ # The former reports "it was defined in Enumerable, but unbound from Array"
+
+ orig_tally = Enumerable.instance_method(:tally)
+ tally == orig_tally
+ # 3.1: false -- because it was unbound from different class
+ # 3.2: true
+
+ # In reality, both are the same, and can be rebound to any class including Enumerable:
+ orig_tally.bind("test".each_char).call
+ # => {"t"=>2, "e"=>1, "s"=>1}
+ tally.bind("test".each_char).call
+ # => {"t"=>2, "e"=>1, "s"=>1} -- on 3.1, this worked, even if tally was "unbound from Array"
+
+ # Therefore, reporting `tally` as belonging to Array and unequal to `orig_tally` was misleading
+ ```
+* **Note:** While it might seem like a weird unnecessary quirk, unbinding methods and then rebinding them to different objects is useful metaprogramming technique when redefining some core methods to preserve and reuse the initial implementation.
+
+### IO and network
+
+#### `IO`: support for timeouts for blocking IO
+
+`IO#timeout` getter and setter were added to the base class, and are respected on blocking operations.
+
+* **Discussion:** [Feature #18630](https://bugs.ruby-lang.org/issues/18630)
+* **Documentation:** [IO#timeout](https://docs.ruby-lang.org/en/3.2/IO.html#method-i-timeout), [IO#timeout=](https://docs.ruby-lang.org/en/3.2/IO.html#method-i-timeout-3D)
+* **Code:**
+ ```ruby
+ STDIN.timeout = 5
+ print "Tell me what: "
+ answer = gets
+ # If you didn't print anything for 5 seconds, this raises:
+ # in `gets': Blocking operation timed out! (IO::TimeoutError)
+
+ STDIN.timeout = nil # to remove the timeout
+ answer = gets
+ # will wait till input appears or process will be killed
+
+ STDIN.timeout = 0
+ answer = gets
+ # Will raise IO::TimeoutError immediately,
+ # useful for quick "take something from input buffer if it isn't empty"
+ ```
+* **Note:** `IO#timeout` in general affects reading and writing operations (including network ones, defined on [Socket](https://docs.ruby-lang.org/en/3.2/Socket.html)). Operations like `IO.open` and `IO#close` are **not** affected.
+
+#### `IO#path`
+
+Any `IO` object can be constructed with additional argument `path:`, which will be available as a `path` attribute.
+
+* **Reason:** `IO` object could be created from low-level file descriptor (for example, returned by some C extension), but there was no way to specify it corresponds to some specific filesystem path.
+* **Discussion:** [Feature #19036](https://bugs.ruby-lang.org/issues/19036)
+* **Documentation:** [IO#Open options](https://docs.ruby-lang.org/en/3.2/IO.html#class-IO-label-Open+Options), [IO#path](https://docs.ruby-lang.org/en/3.2/IO.html#method-i-path)
+* **Code:**
+ ```ruby
+ # Always worked:
+ f = File.open('README.md')
+ f.path #=> 'README.md'
+
+ # IO created from system-level file descriptor (which might've been returned by a C library)
+ io = IO.new(f.fileno)
+ # => #
+ io.path
+ # 3.1: NoMethodError (undefined method `path')
+ # 3.2: => nil
+
+ # IO can't guess file path from the descriptor, but path can be provided explicitly:
+ io = IO.new(f.fileno, path: 'README.md')
+ # => #
+ io.path
+ # => "README.md"
+
+ # One generalization of the new feature was to introspection of standard IO streams:
+ STDOUT.path
+ # 3.1: NoMethodError (undefined method `path')
+ # 3.2: => ""
+ ```
+
+### Exceptions
+
+#### `Exception#detailed_message`
+
+The method can be redefined for providing custom "decoration" of exception messages, without redefining the main `#message`.
+
+* **Reason:** Standard libraries like `did_you_mean` (adds "did you mean _other name_" to `NoMethodError`) or `error_highlight` (printing of failed part of code and highlighting the problematic part) previously adjusted `Exception#message` method. It might not always be convenient: say, if an application wants to benefit from those gems, but also need to report "clear" error messages to a monitoring system, it required workaround. Starting from Ruby 3.2, there is a clear distinction:
+ * `#message` is an original message with which the exception was raised;
+ * `#decorated_message` might be redefined by some libraries or user's code for convenience and better reporting (most probably);
+ * `#full_message` ([introduced](2.5.html#exceptionfull_message) in 2.5) is what the interpreter prints: detailed message + error backtrace.
+* **Discussion:** [Feature #18564](https://bugs.ruby-lang.org/issues/18564)
+* **Documentation:** [Exception#detailed_message](https://docs.ruby-lang.org/en/3.2/Exception.html#method-i-detailed_message)
+* **Code:**
+ ```ruby
+ # Default implementation:
+ begin
+ raise RuntimeError, 'test'
+ rescue => e
+ puts e.message
+ # test
+
+ puts e.detailed_message # adds error class
+ # test (RuntimeError)
+
+ puts e.full_message # adds backtrace
+ # test.rb:3:in `': test (RuntimeError)
+ end
+
+ # NoMethodError employs did_you_mean to lookup for the right name,
+ # and error_highight to show where exactly the error happened:
+ begin
+ 'foo'.lenthg
+ rescue => e
+ puts e.message
+ # undefined method `lenthg' for "foo":String
+
+ puts e.detailed_message # class name + highlighted part of code + "Did you mean?"
+ # undefined method `lenthg' for "foo":String (NoMethodError)
+ #
+ # 'foo'.lenthg
+ # ^^^^^^^
+ # Did you mean? length
+
+ puts e.full_message # all of the above + "where it happened"
+ # test.rb:2:in `': undefined method `lenthg' for "foo":String (NoMethodError)
+ #
+ # 'foo'.lenthg
+ # ^^^^^^^
+ # Did you mean? length
+ end
+
+ # Implement the custom one:
+ class LoadError
+ def detailed_message(highlight: false, **)
+ res = super # invoke the default implementation which will produce message + class name
+ return res unless path.start_with?('vendor/')
+
+ # Provide custom value. Ideally, the code should consider to add some
+ # markup with escape codes for expressiveness if `highlight: true` is passed
+ res + "\n"\
+ " Vendor library `#{path.delete_prefix('vendor/')}' not loaded\n"\
+ " Check our instructions in VENDOR.md"
+ end
+ end
+
+ require 'vendor/tricky'
+ # This will now raise an error which would be printed as...
+ #
+ # in `require': cannot load such file -- vendor/tricky (LoadError)
+ # Vendor library `tricky' not loaded
+ # Check our instructions in VENDOR.md
+ ```
+
+#### `SyntaxError#path`
+
+Returns the path of where the error have happened.
+
+* **Reason:** The feature was introduced by request of [SyntaxSuggest new core library](#standard-library-content-changes). It makes post-processing of SyntaxError easier for this and third-party libraries, say, when it is necessary to analyze the code that errored.
+* **Discussion:** [Feature #19138](https://bugs.ruby-lang.org/issues/19138)
+* **Documentation:** [SyntaxError](https://docs.ruby-lang.org/en/3.2/SyntaxError.html)
+* **Code:**
+ ```ruby
+ # Consider there is 'test.rb' such that:
+ x = 5
+ y = 6
+ z = x**
+ #----
+
+ begin
+ load 'test.rb'
+ rescue SyntaxError => e
+ p e #=> #
+ puts e.path #=> test.rb
+ end
+ ```
+* **Note:** As of 3.2, there is no way to set `path` (unlike other additional exception data like `KeyError#key` that can be set in `#initialize`). As `SyntaxError` is mostly meant to be generate by Ruby parser and not by custom code, that might not be a big problem.
+
+### Concurrency
+
+* For documentation purposes, [Fiber::SchedulerInteface](https://docs.ruby-lang.org/en/3.1/Fiber/SchedulerInterface.html) (3.0-3.1) was renamed to [Fiber::Scheduler](https://docs.ruby-lang.org/en/3.2/Fiber/Scheduler.html) (3.2+). It still serves just a documentation-level abstraction: no real class with such name exists, see [our explanations](https://rubyreferences.github.io/rubychanges/3.0.html#non-blocking-fiber-and-scheduler) in 3.0 changelog.
+
+#### `Fiber` storage
+
+Per-fiber hash-alike storage interface is introduced. It can be set up on Fiber creation, and accessed as a whole via `#storage` accessors, or key-by-key with `Fiber[]` accessors. By default, it is inherited on fiber creation, but can be overridden.
+
+* **Reason:** The official explanation from NEWS is the best: "You should generally consider Fiber storage for any state which you want to be shared implicitly between all fibers and threads created in a given context, e.g. a connection pool, a request id, a logger level, environment variables, configuration, etc."
+* **Discussion:** [Feature #19078](https://bugs.ruby-lang.org/issues/19078)
+* **Documentation:** [Fiber.[]](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-c-5B-5D), [Fiber.[]=](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-c-5B-5D-3D), [Fiber#storage](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-i-storage), [Fiber#storage=](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-i-storage-3D), [Fiber.new](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-c-new)
+* **Code:**
+ ```ruby
+ Fiber[:user] = 'admin'
+ Fiber[:user] #=> "admin"
+ Fiber.current.storage #=> {user: 'admin'}
+ # This will have no effect, storage returns a copy of internal storage
+ Fiber.current.storage[:user] = 'John'
+ # Still the same:
+ Fiber[:user] #=> "admin"
+
+ Fiber.current.storage = {user: 'Jane'}
+ # warning: Fiber#storage= is experimental and may be removed in the future!
+ Fiber[:user] #=> "Jane"
+
+ # Cleaning up the storage
+ Fiber.current.storage = nil
+ Fiber.current.storage #=> {}
+
+ Fiber[:user] = 'admin'
+ f = Fiber.new { puts Fiber[:user] }
+ f.resume # prints "admin", by default the storage is inherited
+ f.storage
+ # raises "Fiber storage can only be accessed from the Fiber it belongs to" (ArgumentError)
+
+ # The storage can be overwritten on creation:
+ Fiber.new(storage: {user: 'Jane'}) { puts Fiber[:user] }.resume
+ # prints "Jane"
+ # or...
+ Fiber.new(storage: nil) { puts Fiber[:user] }.resume
+ # prints empty string
+
+ # The same as default: inherit from the creating fiber:
+ Fiber.new(storage: true) { puts Fiber[:user] }.resume
+ # prints "admin"
+
+ # Even if inherited, fiber storage is isolated between fibers:
+ f = Fiber.new {
+ puts Fiber[:user]
+ Fiber[:user] = 'Amy'
+ }
+ Fiber[:user] = 'Jane'
+ f.resume
+ # prints "admin" from fiber, change in the main fiber didn't affect inherited
+ puts Fiber[:user]
+ # prints "Jane", change in inherited fiber didn't affect the main one
+ ```
+* **Notes:**
+ * Only `Fiber#storage=` is considered experimental; the rest of API is considered stable;
+ * There is an [API discrepancy](https://bugs.ruby-lang.org/issues/19377), currently discussed, between `Fiber[]` (which is class method, reading/writing current fiber's storage) and `Fiber.current.storage` (instance method, but available only on class instance);
+
+#### `Fiber::Scheduler#io_select`
+
+Implements non-blocking `IO.select`
+
+* **Discussion:** [Feature #19060](https://bugs.ruby-lang.org/issues/19060)
+* **Documentation:** [Fiber::Scheduler#io_select](https://docs.ruby-lang.org/en/3.2/Fiber/Scheduler.html#method-i-io_select)
+* **Notes:**
+ * See code examples in [3.0 changelog](3.0.html#non-blocking-fiber-and-scheduler) for general demo of using Fiber Scheduler. As no simple implementation is available, it is complicated to show an example of new hooks in play.
+ * Just to remind: Ruby does not include the default implementation of Fiber Scheduler, but the maintainer of the feature, Samuel Williams, provides one in his gem [Async](https://github.com/socketry/async) which is Ruby 3.2-compatible already.
+
+### Internals
+
+#### `Thread.each_caller_location`
+
+A way for enumerating backtrace entries without instantiating them all.
+
+* **Reason:** There are may contexts when only a small chunk of the backtrace is necessary, but to find this chunk, the whole backtrace needs to be materalized with `#caller_locations`. For example, consider "send to monitoring system the first line in the `app/` that called this (library) query code." In large apps under high load, the call stack might be really large, and cost of its materialization into Ruby objects on frequent calls might be significant. The new method allows to go through stack frames one by one, and break as soon as the necessary one(s) is reached.
+* **Discussion:** [Feature #16663](https://bugs.ruby-lang.org/issues/16663)
+* **Documentation:** [Thread.each_caller_location](https://docs.ruby-lang.org/en/3.2/Thread.html#method-c-each_caller_location)
+* **Code:**
+ ```ruby
+ # test.rb
+ def inner
+ Thread.each_caller_location {
+ p [_1, _1.class]
+ }
+ end
+
+ def outer
+ inner
+ end
+
+ outer
+ # prints:
+ # ["test.rb:8:in `outer'", Thread::Backtrace::Location]
+ # ["test.rb:11:in `'", Thread::Backtrace::Location]
+
+ # More realistic usage:
+ def method_to_debug
+ # ...
+ app_frame = nil
+ Thread.each_caller_location {
+ if _1.path.match?('/app')
+ app_frame = _1
+ break
+ end
+ }
+ Monitoring.notify "Method was invoked by #{app_frame}"
+ # ...
+ end
+ ```
+* **Notes:**
+ * Note that while each item is printed as a regular string, they are actually instances of an utility class [Thread::Backtrace::Location](https://docs.ruby-lang.org/en/3.2/Thread/Backtrace/Location.html).
+ * The method _consciously_ doesn't have block-less version (which should've returned `Enumerator` as Enumerable's method like `#each` or `#map` do): this would defy the point of efficient backtrace analysis _at the current frame_, adding more frames of Enumerable/Enumerator implementations;
+ * For the reason of efficiency, `each_caller_location` returns nothing (again, to avoid materializing unnecessary objects), so if the goal is to find one location, as in example above, or select some part of the call stack, the only way to do it is non-idiomatic code:
+ ```ruby
+ lib = []
+
+ # Goal: take the first caller locations while they are inside our app's lib/ folder:
+ Thread.each_caller_locaton {
+ lib << _1
+ break unless _1.start_with?('lib/')
+ }
+ ```
+
+#### `GC.latest_gc_info`: add `need_major_gc:` key
+
+* **Reason:** The information (whether the next garbage collection would be _minor_ or _major_) might be useful for highload systems, where it might make sense to trigger garbage-collection preemptively if the next one would be major, before entering the performance-critical part of the code.
+* **Discussion:** [GH-6791](https://github.com/ruby/ruby/pull/6791)
+* **Documentation:** [GC.latest_gc_info](https://docs.ruby-lang.org/en/3.2/GC.html#method-c-latest_gc_info) (the possible keys aren't documented)
+* **Code:**
+ ```ruby
+ GC.latest_gc_info
+ # 3.1:
+ # => {:major_by=>nil, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false, :state=>:sweeping}
+ # 3.2:
+ # => {:major_by=>nil, :need_major_by=>nil, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false, :state=>:none}
+
+ # Or:
+ GC.latest_gc_info(:need_major_by) #=> nil
+ ```
+* **Notes:** The author of this changelog is not a GC expert, and the matter is not very well documented, so I only can say that the possible values (besides `nil`), according to feature's code, are `:nofree`, `:oldgen`, `:shady`, `:force`, and they are the same as `major_by:` possibe values. As far as I can guess, `major_by:` describes the latest major GC method, while `need_major_by:` describes the upcoming one; if it is `nil`, the major GC is not upcoming probably?..
+
+#### `ObjectSpace`: dumping object shapes
+
+Object Shapes is a large and interesting new internal object structuring approach which we (being focused on language API) wouldn't explain here. The explanation and discussion can be found at [Feature #18776](https://bugs.ruby-lang.org/issues/18776). The only way the Ruby-level API is affected by the change is a new parameter for `ObjectSpace.dump_all` method, that allows to dump _shapes_ defined so far.
+
+* **Discussion:** [GH-6868](https://github.com/ruby/ruby/pull/6868)
+* **Documentation:** [ObjectSpace#dump_all](https://docs.ruby-lang.org/en/3.2/ObjectSpace.html#method-i-dump_all) (docs not fully updated, though)
+* **Code:**
+ ```ruby
+ require 'objspace'
+
+ # To only output what would be put int ObjectSpace since this point
+ gc_generation = GC.count
+ since_id = RubyVM.stat(:next_shape_id)
+
+ # New shapes are defined when instance vars for objects are set, so let's make one!
+ class User
+ def initialize(id, name)
+ @id = id
+ @name = name
+ end
+ end
+
+ User.new(1, 'Yuki')
+
+ ObjectSpace.dump_all(output: :stdout, since: gc_generation, shapes: since_id)
+ # {"address":"0x7f6490e00da0", "type":"SHAPE", "id":237, "parent_id":5, "depth":3, "shape_type":"IVAR","edge_name":"@id", "edges":1, "memsize":120}
+ # {"address":"0x7f6490e00dc0", "type":"SHAPE", "id":238, "parent_id":237, "depth":4, "shape_type":"IVAR","edge_name":"@name", "edges":0, "memsize":32}
+ ```
+ This reads: setting `@id` creates a new shape (with `"id":237`), and setting `@name` creates the next one, inherited from from that (`"id":238, "parent_id":237`). To understand the deep meaning and consequences of this behavior, though, we'll refer to the [original discussion](https://bugs.ruby-lang.org/issues/18776).
+
+#### `TracePoint#binding` returns `nil` for `c_call`/`c_return`
+
+* **Reason:** See `Kernel#binding` [explanations above](#kernelbinding-raises-if-accessed-not-from-ruby): C methods don't have their own binding, so before Ruby 3.2, `TracePoint#binding` for their call confusingly returned the binding of the first Ruby caller in the call stack.
+* **Discussion:** [Bug #18487](https://bugs.ruby-lang.org/issues/18487)
+* **Documentation:** [TracePoint#binding](https://docs.ruby-lang.org/en/3.2/TracePoint.html#method-i-binding) (still has docs for old behavior, though)
+* **Code:**
+ ```ruby
+ TracePoint.new(:c_call) do |tp|
+ p [tp.method_id, tp.binding, tp.binding&.local_variables]
+ end.enable {
+ x = [5]
+ x.map { }
+ }
+ # In Ruby 3.1, this prints:
+ # [:map, #, [:x]] -- so, we have a binding of surrounding block, not insides of `map`
+ # In Ruby 3.2:
+ # [:map, nil, nil]
+ ```
+
+#### `TracePoint` for block default to trace the current thread
+
+* **Reason:** In block form, the intention of the developer is to trace what's happening in the specified block. In complicated applications, though, other threads might work at the same time and pollute the tracing with unrelated occurrences.
+* **Discussion:** [Bug #16889](https://bugs.ruby-lang.org/issues/16889)
+* **Documentation:** [TracePoint#enable](https://docs.ruby-lang.org/en/3.2/TracePoint.html#method-i-enable).
+* **Code:**
+ ```ruby
+ def test = nil
+
+ other = Thread.start {
+ sleep(0.1) # to give TracePoint time to start
+ test
+ }
+
+ Thread.current.name = 'main'
+ other.name = 'other'
+
+ # Note: each example below needs to restart the "other" thread.
+
+ TracePoint.new(:call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :test
+ end.enable do
+ test
+ other.join
+ end
+ # Ruby 3.1:
+ # Called from #
+ # Called from #
+ # Ruby 3.2:
+ # Called from #
+
+ # The desired thread to trace can be specified explicitly:
+ TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end.enable(target_thread: other) do
+ test
+ other.join
+ end
+ # Ruby 3.1 and 3.2:
+ # Called from #
+
+ # Only block form is affected:
+ tp = TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end
+ tp.enable
+
+ test
+ other.join
+ # Ruby 3.1 & 3.2:
+ # Called from #
+ # Called from #
+
+ # If target for tracing is explicitly specified, all threads are traced:
+ TracePoint.new(:c_call) do |tp|
+ puts "Called from #{Thread.current}" if tp.method_id == :size
+ end.enable(target: method(:test)) do
+ test
+ other.join
+ end
+ # Ruby 3.1 & 3.2:
+ # Called from #
+ # Called from #
+ ```
+
+### `RubyVM::AbstractSyntaxTree`
+
+#### `error_tolerant: true` option for parsing
+
+With this option, parsing can be performed even on incomplete and syntactically incorrect scripts, replacing unparseable parts with `ERROR` token.
+
+* **Reason:** The new option opens road for using Ruby native parser for various language tools working on the fly, while the code is written (like [LSP](https://en.wikipedia.org/wiki/Language_Server_Protocol)) or providing advice and possible fixes on erroneous code. It is important that "official" language parser supported such cases out-of-the-box.
+* **Discussion:** [Feature #19013](https://bugs.ruby-lang.org/issues/19013)
+* **Documentation:** [AbstractSyntaxTree.parse](https://docs.ruby-lang.org/en/3.2/RubyVM/AbstractSyntaxTree.html#method-c-parse)
+* **Code:**
+ ```ruby
+ src = <<~RUBY
+ def test
+ RUBY
+
+ RubyVM::AbstractSyntaxTree.parse(src)
+ # in `parse': syntax error, unexpected end-of-input (SyntaxError)
+
+ root = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true)
+ pp root
+ # Shortening for the sake of this changelog, the structure of the tree would be:
+ #
+ # (SCOPE@1:0-1:8
+ # body:
+ # (DEFN@1:0-1:8
+ # mid: :test
+ # body:
+ # (SCOPE@1:0-1:8
+ # args:
+ # (ARGS@1:8-1:8 ...)
+ # body: nil)))
+ #
+ # E.g. the code is correctly parsed as "a beginning of a method `test`
+ # without a body"
+
+ # The parser also tries to recover from errors in the middle of the script:
+ src = <<~RUBY
+ def bad
+ x +
+ end
+
+ def good
+ puts 'ok'
+ end
+ RUBY
+
+ root = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true)
+ pp root
+ # Shortened output again...
+ #
+ # (SCOPE@1:0-7:3
+ # body:
+ # (BLOCK@1:0-7:3
+ # (DEFN@1:0-3:3
+ # mid: :bad
+ # body:
+ # (SCOPE@1:0-3:3
+ # body: (ERROR@2:2-3:3)))
+ # (DEFN@5:0-7:3
+ # mid: :good
+ # body:
+ # (SCOPE@5:0-7:3
+ # body: (FCALL@6:2-6:13 :puts (LIST@6:7-6:13 (STR@6:7-6:13 "test") nil))))))
+ #
+ # Note the ERROR node in the midle of method :bad, but then properly parsed
+ # body of method :good
+ ```
+* **Notes:**
+ * Recovery not guaranteed
+
+#### `keep_tokens: true` option for parsing
+
+With `keep_tokens: true` option provided, `AbstractSyntaxTree.parse` will attach corresponding code tokens array to each node of the syntax tree.
+
+* **Reason:** As the previous feature, this one is useful for implementing code analysis tools: there are several ways to write code that will produce exactly the same syntax tree; and while it doesn't affect interpreting, it does affect style checking, suggestions etc.
+* **Discussion:** [Feature #19070](https://bugs.ruby-lang.org/issues/19070)
+* **Documentation:** [AbstractSyntaxTree.parse](https://docs.ruby-lang.org/en/3.2/RubyVM/AbstractSyntaxTree.html#method-c-parse), [Node#tokens](https://docs.ruby-lang.org/en/3.2/RubyVM/AbstractSyntaxTree/Node.html#method-i-tokens), [Node#all_tokens](https://docs.ruby-lang.org/en/3.2/RubyVM/AbstractSyntaxTree/Node.html#method-i-all_tokens)
+* **Code:**
+ ```ruby
+ RubyVM::AbstractSyntaxTree.parse("puts 'test'", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :tSP, " ", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]]]
+ RubyVM::AbstractSyntaxTree.parse("puts('test')", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :"(", "(", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]],
+ # [5, :")", ")", [1, 11, 1, 12]]]
+ RubyVM::AbstractSyntaxTree.parse("puts('test', )", keep_tokens: true).tokens
+ # =>
+ # [[0, :tIDENTIFIER, "puts", [1, 0, 1, 4]],
+ # [1, :"(", "(", [1, 4, 1, 5]],
+ # [2, :tSTRING_BEG, "'", [1, 5, 1, 6]],
+ # [3, :tSTRING_CONTENT, "test", [1, 6, 1, 10]],
+ # [4, :tSTRING_END, "'", [1, 10, 1, 11]],
+ # [5, :",", ",", [1, 11, 1, 12]],
+ # [6, :tSP, " ", [1, 12, 1, 13]],
+ # [7, :")", ")", [1, 13, 1, 14]]]
+ ```
+ Note that all three scripts are exactly equivalent execution-wise and will produce the same syntax tree; but from the point of view of code analysis tool, they are different. For example, the first one might cause the suggestion to add parentheses (if that's the preferred style setting), and the last one might imply that the user waits for suggestions for possible local variables to add to output.
+
+## Standard library
+
+By Ruby 3.1 release, most of the standard library is extracted to either _default_ or _bundled_ gems; their development happens in separate repositories, and changelogs are either maintained there, or absent altogether. Either way, their changes aren't mentioned in the combined Ruby changelog, and I'll not be trying to follow all of them.
+
+> **[stdgems.org](https://stdgems.org/)** project has a nice explanations of default and bundled gems concepts, as well as a list of currently gemified libraries and links to their docs.
+
+> "For the rest of us" this means libraries development extracted into separate GitHub repositories, and they are just packaged with main Ruby before release. It means you can do issue/PR to any of them independently, without going through more tough development process of the core Ruby.
+
+A few changes to mention, though:
+
+* [Pathname#lutime](https://docs.ruby-lang.org/en/3.2/Pathname.html#method-i-lutime).
+* [FileUtils.ln_sr](https://docs.ruby-lang.org/en/3.2/FileUtils.html#method-c-ln_sr) and `relative:` option for [FileUtils.ln_s](https://docs.ruby-lang.org/en/3.2/FileUtils.html#method-c-ln_s). Discussion: [Feature #18925](https://bugs.ruby-lang.org/issues/18925).
+* [CGI.escapeURIComponent](https://docs.ruby-lang.org/en/3.2/CGI/Escape.html#method-i-escapeURIComponent) and [CGI.unescapeURIComponent](https://docs.ruby-lang.org/en/3.2/CGI/Escape.html#method-i-unescapeURIComponent) are added. This is an attempt to mitigate discrepancy between various helper method throughout the standard libraries like `URI`, `ERB` and `CGI`. Discussion: [Feature #18822](https://bugs.ruby-lang.org/issues/18822)
+ * The difference with `CGI.escape`/`unescape` is only in encoding and decoding `' '` character (`escape` follows `application/x-www-form-urlencoded` which converts it to `+`, while `escapeURIComponent` follows RFC 3986 and converts it to `'%20'`)
+ * Previously, the goal could've been achieved with `URI.escape`, but it was deprecated since 1.9 and removed in 3.0, being too vague and generic (it actually meant to replace all "unsafe" characters on URI construction).
+ * Unusual for Ruby method names are mimicking well-known JS ones like [encodeURIComponent](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).
+* `Coverage`:
+ * The internal changes in interpreter were made so the standard library would be able to measure code coverage of `eval`-ed code.
+ * Discussion: [Feature #19008](https://bugs.ruby-lang.org/issues/19008).
+ * Docs [Coverage.setup](https://docs.ruby-lang.org/en/3.2/Coverage.html#method-c-setup) (enabled with `eval: true`, or `:all` arguments)
+ * [.supported?](https://docs.ruby-lang.org/en/3.2/Coverage.html#method-c-supported-3F). Discussion: [Feature #19026](https://bugs.ruby-lang.org/issues/19026)
+* There are many awesome changes in Ruby's console IRB, see the gem author's article [What's new in Ruby 3.2's IRB?](https://st0012.dev/whats-new-in-ruby-3-2-irb).
+
+### Version updates
+
+#### Default gems
+
+* [RubyGems](https://github.com/rubygems/RubyGems) 3.4.1
+* [abbrev](https://github.com/ruby/abbrev) 0.1.1
+* [benchmark](https://github.com/ruby/benchmark) 0.2.1
+* [bigdecimal](https://github.com/ruby/bigdecimal) 3.1.3
+* [bundler](https://github.com/rubygems/rubygems/tree/master/bundler) 2.4.1
+* [cgi](https://github.com/ruby/cgi) 0.3.6
+* [csv](https://github.com/ruby/csv) 3.2.6
+* [date](https://github.com/ruby/date) 3.3.3
+* [delegate](https://github.com/ruby/delegate) 0.3.0
+* [did_you_mean](https://github.com/ruby/did_you_mean) 1.6.3
+* [digest](https://github.com/ruby/digest) 3.1.1
+* [drb](https://github.com/ruby/drb) 2.1.1
+* [english](https://github.com/ruby/english) 0.7.2
+* [erb](https://github.com/ruby/erb) 4.0.2
+* [error_highlight](https://github.com/ruby/error_highlight) 0.5.1
+* [etc](https://github.com/ruby/etc) 1.4.2
+* [fcntl](https://github.com/ruby/fcntl) 1.0.2
+* [fiddle](https://github.com/ruby/fiddle) 1.1.1
+* [fileutils](https://github.com/ruby/fileutils) 1.7.0
+* [forwardable](https://github.com/ruby/forwardable) 1.3.3
+* [getoptlong](https://github.com/ruby/getoptlong) 0.2.0
+* [io-console](https://github.com/ruby/io-console) 0.6.0
+* [io-nonblock](https://github.com/ruby/io-nonblock) 0.2.0
+* [io-wait](https://github.com/ruby/io-wait) 0.3.0
+* [ipaddr](https://github.com/ruby/ipaddr) 1.2.5
+* [irb](https://github.com/ruby/irb) 1.6.2
+* [json](https://github.com/flori/json) 2.6.3
+* [logger](https://github.com/ruby/logger) 1.5.3
+* [mutex_m](https://github.com/ruby/mutex_m) 0.1.2
+* [net-http](https://github.com/ruby/net-http) 0.3.2
+* [net-protocol](https://github.com/ruby/net-protocol) 0.2.1
+* [nkf](https://github.com/ruby/nkf) 0.1.2
+* [open](https://github.com/ruby/open)-uri 0.3.0
+* [open3](https://github.com/ruby/open3) 0.1.2
+* [openssl](https://github.com/ruby/openssl) 3.1.0
+* [optparse](https://github.com/ruby/optparse) 0.3.1
+* [ostruct](https://github.com/ruby/ostruct) 0.5.5
+* [pathname](https://github.com/ruby/pathname) 0.2.1
+* [pp](https://github.com/ruby/pp) 0.4.0
+* [pstore](https://github.com/ruby/pstore) 0.1.2
+* [psych](https://github.com/ruby/psych) 5.0.1
+* [racc](https://github.com/ruby/racc) 1.6.2
+* [rdoc](https://github.com/ruby/rdoc) 6.5.0
+* [readline](https://github.com/ruby/readline)-ext 0.1.5
+* [reline](https://github.com/ruby/reline) 0.3.2
+* [resolv](https://github.com/ruby/resolv) 0.2.2
+* [resolv](https://github.com/ruby/resolv)-replace 0.1.1
+* [securerandom](https://github.com/ruby/securerandom) 0.2.2
+* [stringio](https://github.com/ruby/stringio) 3.0.4
+* [strscan](https://github.com/ruby/strscan) 3.0.5
+* [syntax_suggest](https://github.com/ruby/syntax_suggest) 1.0.2
+* [syslog](https://github.com/ruby/syslog) 0.1.1
+* [tempfile](https://github.com/ruby/tempfile) 0.1.3
+* [time](https://github.com/ruby/time) 0.2.1
+* [timeout](https://github.com/ruby/timeout) 0.3.1
+* [tmpdir](https://github.com/ruby/tmpdir) 0.1.3
+* [tsort](https://github.com/ruby/tsort) 0.1.1
+* [un](https://github.com/ruby/un) 0.2.1
+* [uri](https://github.com/ruby/uri) 0.12.0
+* [weakref](https://github.com/ruby/weakref) 0.1.2
+* [win32ole](https://github.com/ruby/win32ole) 1.8.9
+* [yaml](https://github.com/ruby/yaml) 0.2.1
+* [zlib](https://github.com/ruby/zlib) 3.0.0
+
+#### Bundled gems
+
+* [minitest](https://github.com/ruby/minitest) 5.16.3
+* [power_assert](https://github.com/ruby/power_assert) 2.0.3
+* [test-unit](https://github.com/ruby/test-unit) 3.5.7
+* [net-ftp](https://github.com/ruby/net-ftp) 0.2.0
+* [net-imap](https://github.com/ruby/net-imap) 0.3.4
+* [net-pop](https://github.com/ruby/net-pop) 0.1.2
+* [net-smtp](https://github.com/ruby/net-smtp) 0.3.3
+* [rbs](https://github.com/ruby/rbs) 2.8.2
+* [typeprof](https://github.com/ruby/typeprof) 0.21.3
+* [debug](https://github.com/ruby/debug) 1.7.1
+
+### Standard library content changes
+
+* [syntax_suggest](https://github.com/ruby/syntax_suggest) (formerly `dead_end`) gem added. It provides helpful error messages for wrong syntax, trying to guess the place of the error. For example, assuming this `test.rb`:
+ ```ruby
+ def foo
+ [1, 2, 3].each {
+ end
+ ```
+ an attempt to run it with Ruby 3.1 produces:
+ ```
+ test.rb:3: syntax error, unexpected `end'
+ ```
+ while Ruby 3.2 produces:
+ ```
+ Unmatched `{', missing `}' ?
+ 23 def foo
+ > 24 [1, 2, 3].each {
+ 25 end
+ test.rb:3: syntax error, unexpected `end' (SyntaxError)
+ ```
+
diff --git a/_src/evolution.md b/_src/evolution.md
index a491972..33df862 100644
--- a/_src/evolution.md
+++ b/_src/evolution.md
@@ -1,7 +1,7 @@
---
title: Ruby Evolution
prev: /
-next: 3.1
+next: 3.2
description: A very brief list of new significant features that emerged in Ruby programming language since version 2.0 (2013).
image: images/evolution.png
---
@@ -67,7 +67,7 @@ As Ruby is highly object-oriented language, most of the changes can be associate
## Expressions
@@ -82,6 +82,12 @@ As Ruby is highly object-oriented language, most of the changes can be associate
* [2.4](2.4.md#multiple-assignment-allowed-in-conditional-expression) Multiple assignment allowed in conditional expression
* [2.4](2.4.md#toplevel-return) Toplevel `return` to stop interpreting the file immediately; useful for cases like platform-specific classes, where instead of wrapping the whole file in `if SOMETHING_SUPPORTED...`, you can just `return unless SOMETHING_SUPPORTED` at the beginning.
+
+
### Pattern-matching
* [2.7](2.7.md#pattern-matching) **[Pattern-matching](https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rdoc.html) introduced as an experimental feature** that allows to deeply unpack/check nested data structures:
@@ -113,6 +119,23 @@ As Ruby is highly object-oriented language, most of the changes can be associate
```ruby
{a: 1, b: 2} => a:
```
+* [3.2](3.2.html#pattern-matching) Deconstruction added to core and standard library objects: `MatchData`: [#deconstruct](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct) and [#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct_keys)), [Time#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Time.html#method-i-deconstruct_keys), [Date#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Date.html#method-i-deconstruct_keys), [DateTime#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/DateTime.html#method-i-deconstruct_keys):
+ ```ruby
+ 'Ruby 3.2.0'.match(/Ruby (\d)\.(\d)\.(\d)/) => major, minor, patch
+ major #=> "3"
+ minor #=> "2"
+ patch #=> "0"
+
+ if Time.now in year: 2023, month: ..3, wday: 0..5
+ puts "Working day, first quarter!"
+ end
+ ```
+
+
## `Kernel`
@@ -120,12 +143,14 @@ As Ruby is highly object-oriented language, most of the changes can be associate
* **2.0** [#__dir__](https://docs.ruby-lang.org/en/2.0.0/Kernel.html#method-i-__dir__): absolute path to current source file
* **2.0** [#caller_locations](https://docs.ruby-lang.org/en/2.0.0/Kernel.html#method-i-caller_locations) which returns an array of frame information objects, in a form of new class [Thread::Backtrace::Location](https://docs.ruby-lang.org/en/2.0.0/Thread/Backtrace/Location.html)
+ * [3.2](3.2.md#threadeach_caller_location) [Thread.each_caller_location](https://docs.ruby-lang.org/en/3.2/Thread.html#method-c-each_caller_location) as an efficient method to iterate through part of the call stack.
* **2.0** [#caller](https://docs.ruby-lang.org/en/2.0.0/Kernel.html#method-i-caller) accepts second optional argument `n` which specify required caller size.
* **2.2** [#throw](https://docs.ruby-lang.org/en/2.2.0/Kernel.html#method-i-throw) raises `UncaughtThrowError`, subclass of `ArgumentError` when there is no corresponding catch block, instead of `ArgumentError`.
* **2.3** [#loop](https://docs.ruby-lang.org/en/2.3.0/Kernel.html#method-i-loop): when stopped by a `StopIteration` exception, returns what the enumerator has returned instead of `nil`
* [2.5](2.5.md#pp) [#pp](https://docs.ruby-lang.org/en/2.5.0/Kernel.html#method-i-pp) debug printing method is available without `require 'pp'`
* [3.1](3.1.md#kernelload-module-as-a-second-argument) [#load](https://docs.ruby-lang.org/en/3.1/Kernel.html#method-i-load) allows to pass module as a second argument, to load code inside module specified
+
@@ -228,6 +253,19 @@ end
* [3.1](3.1.md#classsubclasses) [Class#subclasses](https://docs.ruby-lang.org/en/3.1/Class.html#method-i-subclasses)
* [3.1](3.1.md#moduleprepend-behavior-change) [Module#prepend](https://docs.ruby-lang.org/en/3.1/Module.html#method-i-prepend) behavior changed to take effect even if the same module is already included.
* [3.1](3.1.md#moduleprivate-public-protected-and-module_function-return-their-arguments) [#private](https://docs.ruby-lang.org/en/3.1/Module.html#method-i-private) and other visibility methods return their arguments, to allow usage in macros like `memoize private def my_method...`
+* [3.2](3.2.md#classattached_object) [Class#attached_object](https://docs.ruby-lang.org/en/3.2/Class.html#method-i-attached_object) for singleton classes.
+* [3.2](3.2.md#moduleconst_added) [Module#const_added](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-const_added) hook method.
+* [3.2](3.2.md#moduleundefined_instance_methods) [Module#undefined_instance_methods](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-undefined_instance_methods)
+* [3.2](3.2.md#behavior-of-module-reopeningredefinition-with-included-modules-changed) Behavior of module reopening/redefinition with included modules changed: top-level ones wouldn't conflict with included anymore:
+ ```ruby
+ require 'net/http'
+ include Net
+
+ # Ruby 3.1: Reopens Net::HTTP
+ # Ruby 3.2: Defines new top-level class HTTP
+ class HTTP
+ end
+ ```
## Procs, blocks and `Proc` class
@@ -337,6 +385,7 @@ This section lists changes in how methods are defined and invoked, as well as ne
```ruby
[1, 2, 3].map { _1 * 100 } # => 100, 200, 300
```
+* [3.2](3.2.md#procparameters-new-keyword-argument-lambda-truefalse) [Proc#parameters](https://docs.ruby-lang.org/en/3.2/Proc.html#method-i-parameters): new keyword argument `lambda: true/false`, improving introspection of whether arguments have default values or they are just optional because all `proc` arguments are.
## `Comparable`
@@ -391,6 +441,8 @@ Included in many classes to implement comparison methods. Once class defines a m
* [2.5](2.5.md#sqrt) [Integer.sqrt](https://ruby-doc.org/core-2.5.0/Integer.html#method-c-sqrt)
* [2.7](2.7.md#integer-with-range) [Integer#[]](https://ruby-doc.org/core-2.7.0/Integer.html#method-i-5B-5D) supports range of bits
* [3.1](3.1.md#integertry_convert) [Integer.try_convert](https://docs.ruby-lang.org/en/3.1/Integer.html#method-c-try_convert)
+* [3.2](3.2.md#integerceildiv) [Integer#ceildiv](https://docs.ruby-lang.org/en/3.2/Integer.html#method-i-ceildiv)
+
@@ -471,6 +529,29 @@ Included in many classes to implement comparison methods. Once class defines a m
```
* [3.1](3.1.md#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) Warning on passing keywords to a non-keyword-initialized struct
* [3.1](3.1.md#structclasskeyword_init) [Struct.keyword_init?](https://docs.ruby-lang.org/en/3.1/Struct.html#method-c-keyword_init-3F)
+* [3.2](3.2.md#struct-can-be-initialized-by-keyword-arguments-by-default) [Struct.new](https://docs.ruby-lang.org/en/3.2/Struct.html#method-c-new) accepts both positional and keyword arguments by default, unless `keyword_init: true` or `false` was explicitly specified.
+
+## `Data`
+
+* [3.2](3.2.md#data-new-immutable-value-object-class) **[Data](https://docs.ruby-lang.org/en/3.2/Data.html): new immutable value object class introduced.** It has a stricter and leander interface than `Struct`:
+ ```ruby
+ Point = Data.define(:x, :y)
+
+ # Both positional and keyword arguments can be used
+ p1 = Point.new(1, 0) #=> #
+ p2 = Point.new(x: 0, y: 1) #=> #
+
+ # all arguments are mandatory
+ Point.new(1) # missing keyword: :y (ArgumentError)
+
+ # there is no setters or any other way to change already created object
+ p1.x = 5 # undefined method `x=' for # (NoMethodError)
+ p1.instance_variable_set('@z', 100) # can't modify frozen Point: # (FrozenError)
+
+ # #with method can be used to construct new instances,
+ # replacing only parts of the data:
+ p1.with(y: 100) #=> #
+ ```
## `Time`
@@ -494,6 +575,7 @@ Included in many classes to implement comparison methods. Once class defines a m
Time.new(2022, 7, 1, 14, 30, in: '+05:00')
# => 2022-07-01 14:30:00 +0500
```
+* [3.2](3.2.md#timenew-can-parse-a-string) **[Time.new](https://docs.ruby-lang.org/en/3.2/Time.html#method-c-new) can parse a string** (stricter and more robust than `Time.parse` of the standard library)
### `Set`
-`Set` was a part of the standard library, but since Ruby 3.2 it will become part of Ruby core. A more efficient implementation (currently `Set` is implemented in Ruby, and stores data in `Hash` inside), and a separate set literal is up for discussion. That's why we list `Set`'s changes briefly here.
+`Set` was a part of the standard library, but since Ruby 3.2 it became part of Ruby core. A more efficient implementation (currently `Set` is implemented in Ruby, and stores data in `Hash` inside), and a separate set literal is up for discussion. That's why we list `Set`'s changes are listed briefly here.
* **2.1** [#intersect?](https://docs.ruby-lang.org/en/2.1.0/Set.html#method-i-intersect-3F) and [#disjoint?](https://docs.ruby-lang.org/en/2.1.0/Set.html#method-i-disjoint-3F)
* **2.4** [#compare_by_identity](https://docs.ruby-lang.org/en/2.4.0/Set.html#method-i-compare_by_identity) and [#compare_by_identity?](https://docs.ruby-lang.org/en/2.4.0/Set.html#method-i-compare_by_identity-3F)
@@ -694,6 +779,7 @@ Included in many classes to implement comparison methods. Once class defines a m
* **3.0** `SortedSet` (that was a part of `set` standard library before) has been removed for dependency and performance reasons (it silently depended upon `rbtree` gem).
* **3.0** [#join](https://docs.ruby-lang.org/en/3.0/Set.html#method-i-join) is added as a shorthand for `.to_a.join`.
* **3.0** [#<=>](https://docs.ruby-lang.org/en/3.0/Set.html#method-i-3C-3D-3E) generic comparison operator (separate operators like `#<` or `#>` have been worked in previous versions, too)
+* [3.2](3.2.md#set-became-a-built-in-class) **[Set](https://docs.ruby-lang.org/en/3.2/Set.html) became a built-in class**
+### `ObjectSpace`
+
+* [3.2](3.2.md#objectspace-dumping-object-shapes) [ObjectSpace#dump_all](https://docs.ruby-lang.org/en/3.2/ObjectSpace.html#method-i-dump_all) allow to dump _object shapes_, a concept introduced in Ruby 3.2.
+
+
## Deeper topics
### Refinements
@@ -941,7 +1051,9 @@ Refinements are hygienic replacement for reopening of classes and modules. They
* [2.6](2.6.md#refinements-improved-visibility) Refined methods are achievable with `#public_send` and `#respond_to?`, and implicit `#to_proc`.
* [2.7](2.7.md#refinements-in-methodinstance_method) Refined methods are achievable with `#method`/`#instance_method`
* [3.1](3.1.md#refinement-class) **[Refinement](https://docs.ruby-lang.org/en/3.1/Refinement.html) class** representing the `self` inside the `refine` statement. In particular, new method [#import_methods](https://docs.ruby-lang.org/en/3.1/Refinement.html#method-i-import_methods) became available inside `#refine` providing some (incomplete) remedy for inability to `#include` modules while refining some class.
-
+* [3.2](3.2.md#modulerefinements) [Module#refinements](https://docs.ruby-lang.org/en/3.2/Module.html#method-i-refinements) to introspect which refinements some module defines;
+ * [3.2](3.2.md#refinementrefined_class) [Refinement#refined_class](https://docs.ruby-lang.org/en/3.2/Refinement.html#method-i-refined_class) to see what class/module they refine; and
+ * [3.2](3.2.md#moduleused_refinements) [Module.used_refinements](https://docs.ruby-lang.org/en/3.2/Module.html#method-c-used_refinements) to check which refinements are active in the current context.
### Freezing
@@ -957,6 +1069,7 @@ Freezing of object makes its state immutable. The important thing about freezing
* **2.2** `nil`/`true`/`false` objects are frozen.
* **2.3** [String#+@](https://docs.ruby-lang.org/en/2.3.0/String.html#method-i-2B-40) and [#-@](https://docs.ruby-lang.org/en/2.3.0/String.html#method-i-2D-40) are added to get mutable/frozen strings.
* _The methods are mnemonical to those using Celsius temperature scale, where 0 is freezing point, so any "minus-something" is frozen while "plus-something" is not._
+ * [2.5](2.5.md#string--optimized-for-memory-preserving) [String#-@](https://docs.ruby-lang.org/en/2.5.0/String.html#method-i-2D-40) deduplicates frozen strings.
* [2.4](2.4.md#objectclonefreeze-false) [Object#clone](https://ruby-doc.org/core-2.4.0/Object.html#method-i-clone): `freeze: false` argument to receive unfrozen clone of a frozen object
* [3.0](3.0.md#objectclonefreeze-true) `freeze: true` also works, for consistency.
* [3.0](3.0.md#objectclone-passes-freeze-argument-to-initialize_clone) `freeze:` argument is passed to `#initialize_clone`
@@ -964,6 +1077,8 @@ Freezing of object makes its state immutable. The important thing about freezing
* [3.0](3.0.md#interpolated-string-literals-are-no-longer-frozen-when--frozen-string-literal-true-is-used) Interpolated String literals are no longer frozen when [`# frozen-string-literal: true` pragma](https://docs.ruby-lang.org/en/3.0.0/syntax/comments_rdoc.html#label-frozen_string_literal+Directive) is used
* [3.0](3.0.md#regexp-and-range-objects-are-frozen) `Regexp` and `Range` objects are frozen
* [3.0](3.0.md#symbolname) [Symbol#name](https://docs.ruby-lang.org/en/3.0.0/Symbol.html#method-i-name) method that returns a frozen string equivalent of the symbol (`Symbol#to_s` returns mutable one, and changing it to be frozen would cause too much incompatibilities)
+* [3.2](3.2.md#stringdedup-as-an-alias-for--string) [String#dedup](https://docs.ruby-lang.org/en/3.2/String.html#method-i-dedup) as an alias for `-"string"`
+
## Appendix: Covered Ruby versions release dates
@@ -977,5 +1092,5 @@ Freezing of object makes its state immutable. The important thing about freezing
* [2.7](2.7.md) — 2019
* [3.0](3.0.md) — 2020
* [3.1](3.1.md) — 2021
-
+* [3.2](3.2.md) — 2022
diff --git a/_src/lib/render.rb b/_src/lib/render.rb
index 0dabcc1..f34980d 100644
--- a/_src/lib/render.rb
+++ b/_src/lib/render.rb
@@ -1,11 +1,11 @@
class Render < FileProcessor
- TRACKER_LINK_RE = %r{\[(?Bug|Feature|Misc) \#(?\d+)\]\(https://bugs\.ruby-lang\.org/issues/(?\d+(\#.+)?)\)}
+ TRACKER_LINK_RE = %r{\[(?Bug|Feature|Misc) \#(?\d+)(?\#note-\d+)?\]\(https://bugs\.ruby-lang\.org/issues/(?\d+(\#.+)?)(\#note-\d+)?\)}
DOC_URLS = '(?:https://ruby-doc\.org|https://docs.ruby-lang.org)'
def call
text
.gsub('<>', File.mtime(path).strftime('%b %d, %Y'))
- .gsub(/\[(Bug|Feature|Misc) \#\d+\]\(.+?\)/, &method(:process_link))
+ .gsub(/\[(Bug|Feature|Misc) \#\d+.*?\]\(.+?\)/, &method(:process_link))
.gsub( # links to official docs to just nicer links (with icon)
%r{\[([^\[\]]+ [^\[\]]+)\]\((#{DOC_URLS}.+?)\)},
'\\1'
@@ -30,8 +30,9 @@ def call
def process_link(link)
m = link.match(TRACKER_LINK_RE) or fail("Wrong link: #{link}")
- kind, num, num2 = m.values_at(:kind, :num, :num2)
+ kind, num, num2, note = m.values_at(:kind, :num, :num2, :note)
num == num2.sub(/\#.+$/, '') or fail "Wrong link: #{link}"
- %{#{kind} ##{num}}
+ note_render = "#{note}" if note
+ %{#{kind} ##{num}#{note_render}}
end
end
diff --git a/_src/lib/toc.rb b/_src/lib/toc.rb
index e954789..4754e51 100644
--- a/_src/lib/toc.rb
+++ b/_src/lib/toc.rb
@@ -75,8 +75,8 @@ def nest_headers(headers, level = 1)
def rss_fields
return {} if version == 'evolution'
- pub = text[/\*\*This document first published:\*\* (.+)\n/, 1] or fail "Published at not found"
- desc = text[/\#\# Highlights\n(.+?)\n\#\# /m, 1] or fail "Description not found"
+ pub = text[/\*\*This document first published:\*\* (.+)\n/, 1] or fail "Published at not found: #{path}"
+ desc = text[/\#\# Highlights\n(.+?)\n\#\# /m, 1] or fail "Description not found: #{path}"
desc = desc
.gsub(/\[(.+?)\]\(.+?\)/, '\1') # remove links
.then { |desc| RSS_DESCRIPTION % [desc, version] }
diff --git a/evolution.md b/evolution.md
index 601528c..3f00c23 100644
--- a/evolution.md
+++ b/evolution.md
@@ -1,7 +1,7 @@
---
title: Ruby Evolution
prev: /
-next: 3.1
+next: 3.2
description: A very brief list of new significant features that emerged in Ruby programming language since version 2.0 (2013).
image: images/evolution.png
---
@@ -67,7 +67,7 @@ As Ruby is highly object-oriented language, most of the changes can be associate
## Expressions[](#expressions)
@@ -82,6 +82,12 @@ As Ruby is highly object-oriented language, most of the changes can be associate
* [2.4](2.4.md#multiple-assignment-allowed-in-conditional-expression) Multiple assignment allowed in conditional expression
* [2.4](2.4.md#toplevel-return) Toplevel `return` to stop interpreting the file immediately; useful for cases like platform-specific classes, where instead of wrapping the whole file in `if SOMETHING_SUPPORTED...`, you can just `return unless SOMETHING_SUPPORTED` at the beginning.
+
+
### Pattern-matching[](#pattern-matching)
* [2.7](2.7.md#pattern-matching) **Pattern-matching introduced as an experimental feature** that allows to deeply unpack/check nested data structures:
@@ -113,6 +119,23 @@ As Ruby is highly object-oriented language, most of the changes can be associate
```ruby
{a: 1, b: 2} => a:
```
+* [3.2](3.2.html#pattern-matching) Deconstruction added to core and standard library objects: `MatchData`: #deconstruct and #deconstruct_keys), Time#deconstruct_keys, Date#deconstruct_keys, DateTime#deconstruct_keys:
+ ```ruby
+ 'Ruby 3.2.0'.match(/Ruby (\d)\.(\d)\.(\d)/) => major, minor, patch
+ major #=> "3"
+ minor #=> "2"
+ patch #=> "0"
+
+ if Time.now in year: 2023, month: ..3, wday: 0..5
+ puts "Working day, first quarter!"
+ end
+ ```
+
+
## `Kernel`[](#kernel)
@@ -120,12 +143,14 @@ As Ruby is highly object-oriented language, most of the changes can be associate
* **2.0** #__dir__: absolute path to current source file
* **2.0** #caller_locations which returns an array of frame information objects, in a form of new class Thread::Backtrace::Location
+ * [3.2](3.2.md#threadeach_caller_location) Thread.each_caller_location as an efficient method to iterate through part of the call stack.
* **2.0** #caller accepts second optional argument `n` which specify required caller size.
* **2.2** #throw raises `UncaughtThrowError`, subclass of `ArgumentError` when there is no corresponding catch block, instead of `ArgumentError`.
* **2.3** #loop: when stopped by a `StopIteration` exception, returns what the enumerator has returned instead of `nil`
* [2.5](2.5.md#pp) #pp debug printing method is available without `require 'pp'`
* [3.1](3.1.md#kernelload-module-as-a-second-argument) #load allows to pass module as a second argument, to load code inside module specified
+
@@ -228,6 +253,19 @@ end
* [3.1](3.1.md#classsubclasses) Class#subclasses
* [3.1](3.1.md#moduleprepend-behavior-change) Module#prepend behavior changed to take effect even if the same module is already included.
* [3.1](3.1.md#moduleprivate-public-protected-and-module_function-return-their-arguments) #private and other visibility methods return their arguments, to allow usage in macros like `memoize private def my_method...`
+* [3.2](3.2.md#classattached_object) Class#attached_object for singleton classes.
+* [3.2](3.2.md#moduleconst_added) Module#const_added hook method.
+* [3.2](3.2.md#moduleundefined_instance_methods) Module#undefined_instance_methods
+* [3.2](3.2.md#behavior-of-module-reopeningredefinition-with-included-modules-changed) Behavior of module reopening/redefinition with included modules changed: top-level ones wouldn't conflict with included anymore:
+ ```ruby
+ require 'net/http'
+ include Net
+
+ # Ruby 3.1: Reopens Net::HTTP
+ # Ruby 3.2: Defines new top-level class HTTP
+ class HTTP
+ end
+ ```
## Procs, blocks and `Proc` class[](#procs-blocks-and-proc-class)
@@ -337,6 +385,7 @@ This section lists changes in how methods are defined and invoked, as well as ne
```ruby
[1, 2, 3].map { _1 * 100 } # => 100, 200, 300
```
+* [3.2](3.2.md#procparameters-new-keyword-argument-lambda-truefalse) Proc#parameters: new keyword argument `lambda: true/false`, improving introspection of whether arguments have default values or they are just optional because all `proc` arguments are.
## `Comparable`[](#comparable)
@@ -391,6 +441,8 @@ Included in many classes to implement comparison methods. Once class defines a m
* [2.5](2.5.md#sqrt) Integer.sqrt
* [2.7](2.7.md#integer-with-range) Integer#[] supports range of bits
* [3.1](3.1.md#integertry_convert) Integer.try_convert
+* [3.2](3.2.md#integerceildiv) Integer#ceildiv
+
@@ -471,6 +529,29 @@ Included in many classes to implement comparison methods. Once class defines a m
```
* [3.1](3.1.md#warning-on-passing-keywords-to-a-non-keyword-initialized-struct) Warning on passing keywords to a non-keyword-initialized struct
* [3.1](3.1.md#structclasskeyword_init) Struct.keyword_init?
+* [3.2](3.2.md#struct-can-be-initialized-by-keyword-arguments-by-default) Struct.new accepts both positional and keyword arguments by default, unless `keyword_init: true` or `false` was explicitly specified.
+
+## `Data`[](#data)
+
+* [3.2](3.2.md#data-new-immutable-value-object-class) **Data: new immutable value object class introduced.** It has a stricter and leander interface than `Struct`:
+ ```ruby
+ Point = Data.define(:x, :y)
+
+ # Both positional and keyword arguments can be used
+ p1 = Point.new(1, 0) #=> #
+ p2 = Point.new(x: 0, y: 1) #=> #
+
+ # all arguments are mandatory
+ Point.new(1) # missing keyword: :y (ArgumentError)
+
+ # there is no setters or any other way to change already created object
+ p1.x = 5 # undefined method `x=' for # (NoMethodError)
+ p1.instance_variable_set('@z', 100) # can't modify frozen Point: # (FrozenError)
+
+ # #with method can be used to construct new instances,
+ # replacing only parts of the data:
+ p1.with(y: 100) #=> #
+ ```
## `Time`[](#time)
@@ -494,6 +575,7 @@ Included in many classes to implement comparison methods. Once class defines a m
Time.new(2022, 7, 1, 14, 30, in: '+05:00')
# => 2022-07-01 14:30:00 +0500
```
+* [3.2](3.2.md#timenew-can-parse-a-string) **Time.new can parse a string** (stricter and more robust than `Time.parse` of the standard library)
### `Set`[](#set)
-`Set` was a part of the standard library, but since Ruby 3.2 it will become part of Ruby core. A more efficient implementation (currently `Set` is implemented in Ruby, and stores data in `Hash` inside), and a separate set literal is up for discussion. That's why we list `Set`'s changes briefly here.
+`Set` was a part of the standard library, but since Ruby 3.2 it became part of Ruby core. A more efficient implementation (currently `Set` is implemented in Ruby, and stores data in `Hash` inside), and a separate set literal is up for discussion. That's why we list `Set`'s changes are listed briefly here.
* **2.1** #intersect? and #disjoint?
* **2.4** #compare_by_identity and #compare_by_identity?
@@ -694,6 +779,7 @@ Included in many classes to implement comparison methods. Once class defines a m
* **3.0** `SortedSet` (that was a part of `set` standard library before) has been removed for dependency and performance reasons (it silently depended upon `rbtree` gem).
* **3.0** #join is added as a shorthand for `.to_a.join`.
* **3.0** #<=> generic comparison operator (separate operators like `#<` or `#>` have been worked in previous versions, too)
+* [3.2](3.2.md#set-became-a-built-in-class) **Set became a built-in class**
+### `ObjectSpace`[](#objectspace)
+
+* [3.2](3.2.md#objectspace-dumping-object-shapes) ObjectSpace#dump_all allow to dump _object shapes_, a concept introduced in Ruby 3.2.
+
+
## Deeper topics[](#deeper-topics)
### Refinements[](#refinements)
@@ -941,7 +1051,9 @@ Refinements are hygienic replacement for reopening of classes and modules. They
* [2.6](2.6.md#refinements-improved-visibility) Refined methods are achievable with `#public_send` and `#respond_to?`, and implicit `#to_proc`.
* [2.7](2.7.md#refinements-in-methodinstance_method) Refined methods are achievable with `#method`/`#instance_method`
* [3.1](3.1.md#refinement-class) **Refinement class** representing the `self` inside the `refine` statement. In particular, new method #import_methods became available inside `#refine` providing some (incomplete) remedy for inability to `#include` modules while refining some class.
-
+* [3.2](3.2.md#modulerefinements) Module#refinements to introspect which refinements some module defines;
+ * [3.2](3.2.md#refinementrefined_class) Refinement#refined_class to see what class/module they refine; and
+ * [3.2](3.2.md#moduleused_refinements) Module.used_refinements to check which refinements are active in the current context.
### Freezing[](#freezing)
@@ -957,6 +1069,7 @@ Freezing of object makes its state immutable. The important thing about freezing
* **2.2** `nil`/`true`/`false` objects are frozen.
* **2.3** String#+@ and #-@ are added to get mutable/frozen strings.
* _The methods are mnemonical to those using Celsius temperature scale, where 0 is freezing point, so any "minus-something" is frozen while "plus-something" is not._
+ * [2.5](2.5.md#string--optimized-for-memory-preserving) String#-@ deduplicates frozen strings.
* [2.4](2.4.md#objectclonefreeze-false) Object#clone: `freeze: false` argument to receive unfrozen clone of a frozen object
* [3.0](3.0.md#objectclonefreeze-true) `freeze: true` also works, for consistency.
* [3.0](3.0.md#objectclone-passes-freeze-argument-to-initialize_clone) `freeze:` argument is passed to `#initialize_clone`
@@ -964,6 +1077,8 @@ Freezing of object makes its state immutable. The important thing about freezing
* [3.0](3.0.md#interpolated-string-literals-are-no-longer-frozen-when--frozen-string-literal-true-is-used) Interpolated String literals are no longer frozen when `# frozen-string-literal: true` pragma is used
* [3.0](3.0.md#regexp-and-range-objects-are-frozen) `Regexp` and `Range` objects are frozen
* [3.0](3.0.md#symbolname) Symbol#name method that returns a frozen string equivalent of the symbol (`Symbol#to_s` returns mutable one, and changing it to be frozen would cause too much incompatibilities)
+* [3.2](3.2.md#stringdedup-as-an-alias-for--string) String#dedup as an alias for `-"string"`
+
## Appendix: Covered Ruby versions release dates[](#appendix-covered-ruby-versions-release-dates)
@@ -977,5 +1092,5 @@ Freezing of object makes its state immutable. The important thing about freezing
* [2.7](2.7.md) — 2019
* [3.0](3.0.md) — 2020
* [3.1](3.1.md) — 2021
-
+* [3.2](3.2.md) — 2022
diff --git a/index.md b/index.md
index c3f8818..c0670a7 100644
--- a/index.md
+++ b/index.md
@@ -8,7 +8,7 @@ permalink: "/index.html"
This site is dedicated to history of [Ruby language](https://www.ruby-lang.org/) evolution. Basically, it is just the same information that each Ruby version's [NEWS](https://github.com/ruby/ruby/blob/master/NEWS.md) file contains, just in more readable and informative manner.
-> Latest version: 3.1 ([3.0](3.0.html) − [2.7](2.7.html) − [2.6](2.6.html) −[2.5](2.5.html) − [2.4](2.4.html))
**[Ruby Evolution](evolution.html)**: bird-eye view on all significant changes 2.0−3.1, grouped by topic.
+> Latest version: 3.2 ([3.1](3.1.html) − [3.0](3.0.html) − [2.7](2.7.html) − [2.6](2.6.html) −[2.5](2.5.html) − [2.4](2.4.html))
**[Ruby Evolution](evolution.html)**: bird-eye view on all significant changes 2.0−3.2, grouped by topic.
Maintainer's write-up on the creation of the changelogs:
1. [What you can learn by merely writing a programming language changelog](https://zverok.space/blog/2022-01-06-changelog.html)
@@ -33,13 +33,14 @@ Maintainer's write-up on the creation of the changelogs:
* Maybe "Highlights" section at the top of each version changelog is more subjective than the rest of the content, but it is totally optional, you can skip it.
* The site is dedicated to the **language**, not its **implementation(s)**, therefore at the moment it **does not** include the description of MRI implementation changes, optimizations and internals.
* ...and also some minor _behavior_ changes are excluded.
-* I want to _eventually_ cover Ruby versions down to 1.8, or maybe even earlier, but it is currently work-in-progress, with the first priority of the recent release of **[Ruby 2.6](2.6.html)**, and then going down version by version in my free time.
+* I want to _eventually_ cover Ruby versions down to 1.8, or maybe even earlier, but it is currently work-in-progress. I started doing it at the wake of **[Ruby 2.6](2.6.html)**, and maintaining the changelog since.
* **UPD 2019-06-06:** **[Ruby 2.5](2.5.html)** is now covered too. Despite being 1.5 years old news, I believe it is still important to cover it in the same manner as the recent version was.
* **UPD 2019-10-14:** **[Ruby 2.4](2.4.html)** changelog added, and some other content changed. See [History](/History.html) for detail.
* **UPD 2019-12-27:** Newly released **[Ruby 2.7](2.7.html)** changelog added.
* **UPD 2020-12-25:** Newly released **[Ruby 3.0](3.0.html)** changelog added.
* **UPD 2022-01-05:** Newly released **[Ruby 3.1](3.1.html)** changelog added.
* **UPD 2022-06-09:** Newly released **[Ruby Evolution](evolution.html)** bird-eye view added.
+* **UPD 2023-02-04:** Newly released **[Ruby 3.2](3.2.html)** changelog added.
_The source of the site can be found at [GitHub](https://github.com/rubyreferences/rubychanges). See also the [Contributing](Contributing.html) section._