Bug #22420
open`require "strscan"` no longer activates the default gem, so a newer installed strscan is ignored and later loaded twice
Description
Since Ruby 4.0.0, require "strscan" loads the default version from rubylibdir even when a newer strscan gem is installed, and does not activate the default gem. If the gem is activated later, its extension is loaded on top of the one already loaded.
Reproduction¶
$ ruby -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]
$ gem install strscan # 3.1.8, newer than the default 3.1.6
$ ruby -e 'require "strscan"; p StringScanner::Version; p Gem.loaded_specs["strscan"]&.version'
"3.1.6"
nil
$ ruby -e 'require "strscan"; gem "strscan"; require "strscan"; p StringScanner::Version'
.../gems/strscan-3.1.8/lib/strscan.bundle: warning: already initialized constant StringScanner::Version
.../gems/strscan-3.1.8/lib/strscan.bundle: warning: already initialized constant StringScanner::Id
"3.1.8"
The second case happens in practice when a gem depending on strscan (e.g. rouge) is required after strscan.
Expected¶
Same as Ruby 3.3:
$ ruby -e 'require "strscan"; p StringScanner::Version; p Gem.loaded_specs["strscan"]&.version'
"3.1.8"
#<Gem::Version "3.1.8">
$ ruby -e 'require "strscan"; gem "strscan"; require "strscan"; p StringScanner::Version'
"3.1.8"
Cause¶
The installed default gemspec has wrong files. It has lib/lib/strscan/strscan.rb, and lacks strscan.bundle:
$ ruby -e 'p Gem::Specification.load(Dir[Gem.default_specifications_dir + "/strscan-*.gemspec"][0]).files.grep_v(/\Adoc\//)'
[".rdoc_options", "COPYING", "LICENSE.txt", "ext/strscan/extconf.rb", "ext/strscan/strscan.c", "lib/lib/strscan/strscan.rb"]
So Gem.register_default_spec maps no strscan feature to the default gem.
Both come from load_gemspec in tool/rbinstall.rb:
- It prepends
lib/to every.rbentry that does not exist relative to the current directory. This is meant for the files collected from the build (relative to the gem's lib directory), but is also applied to the files listed by the gemspec itself (relative to the gem root), so they becomelib/lib/.... - The gemspec is evaluated with
bindingofload_gemspec, and strscan.gemspec assigns a local variablefiles, which overwrites the argument holding the collected files. Sostrscan.bundleis dropped.
require "io/console" is also no longer mapped to the default gem for the same reason. Other default gems (json, openssl, psych, date, ...) get lib/lib/... entries as well, though their main features are still mapped.
Ruby 3.4 and earlier replaced spec.files with the collected files, so this is a regression from the changes to load_gemspec for 4.0 (7ce339244953a6c3add543854292c61e9f5bc14b, d6b40320b2cfecf9802e664a4869353d195fcc6d).
Updated by ksss (Yuki Kurihara) about 14 hours ago
I opened a pull request: https://github.com/ruby/ruby/pull/19275