Restore blockless overloads for optional-block methods - #3060
Open
mybys wants to merge 1 commit into
Open
Conversation
`Array#sum`, `Array#count` and `Hash#transform_keys!` were changed to use
optional blocks (`?{ ... }`) in 4.1. Steep cannot check a block-pass
argument against an optional block: the expected type becomes a union
with `nil`, which the special case for `Proc` values does not cover
(soutaro/steep#1207). As a result `array.sum(&:amount)`,
`array.count(&:even?)` and friends stopped type checking after 4.0.3,
even though the same call with a literal block is fine.
Spell out the blockless overload again so that block-pass arguments match
the overload with a required block, as they did before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mybys
marked this pull request as draft
August 3, 2026 01:24
mybys
marked this pull request as ready for review
August 3, 2026 04:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Array#sum,Array#countandHash#transform_keys!were converted from explicit overloads to optional blocks (?{ ... }) in 4.1. This makes block-pass arguments fail to type check under Steep, so code that worked with 4.0.3 now reportsRuby::BlockTypeMismatch. This PR restores the blockless overload for those three methods and adds atest/typecheckcase covering them.Motivation
Steep cannot check a block-pass argument against an optional block. The expected type becomes a union with
nil, and the special case that lets a plain::Proc(whatSymbol#to_procreturns) satisfy a block type does not apply to the union — see soutaro/steep#1207, open since 2024.With
masteras of 7534c7e:The same call with a literal block is accepted, and
[1, 2, 3].map(&:to_r)is accepted too becauseEnumerable#mapdeclares a required block. Writing the blockless case as its own overload restores the pre-4.1 behaviour: the block-pass argument then matches the overload with a required block.This was found while upgrading an application from 4.0.3 to 4.1.1, where it produced 37
Ruby::BlockTypeMismatcherrors, all fromsum(&:some_method).Changes
core/array.rbs: splitArray#sumandArray#countback into blockless and block-taking overloads.core/hash.rbs: same for the(hash[K, K] replacements)branch ofHash#transform_keys!.test/typecheck/block_pass/: newsteep checkcase that fails onmasterand passes here.The overloads accept exactly the same calls as the optional-block form, so this is not a change in what type checks apart from block-pass arguments.
Not included
Module#initializewas converted the same way in 4.1, but restoring its overloads does not help: the block gained a[self: self]binding, and Steep rejects a::Procagainst a self-bound block whether it is required or optional.Class#initializealready used?{ ... }in 4.0.3, so it is not a regression. Both are left alone.Testing
rake typecheck_test— passes, including the newtest/typecheck/block_pass.rake test— 967 tests, 0 failures.rake test/stdlib/Array_test.rb,rake test/stdlib/Hash_test.rb— no new failures. (Array_testhas a pre-existing error forArray#rfindwhen run on Ruby 3.4, present onmastertoo.)rake validateandbin/steep check— pass.🤖 Generated with Claude Code