Skip to content

Restore blockless overloads for optional-block methods - #3060

Open
mybys wants to merge 1 commit into
ruby:masterfrom
mybys:restore-blockless-overloads
Open

Restore blockless overloads for optional-block methods#3060
mybys wants to merge 1 commit into
ruby:masterfrom
mybys:restore-blockless-overloads

Conversation

@mybys

@mybys mybys commented Aug 3, 2026

Copy link
Copy Markdown

Summary

Array#sum, Array#count and Hash#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 reports Ruby::BlockTypeMismatch. This PR restores the blockless overload for those three methods and adds a test/typecheck case 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 (what Symbol#to_proc returns) satisfy a block type does not apply to the union — see soutaro/steep#1207, open since 2024.

With master as of 7534c7e:

[1, 2, 3].sum(&:to_r)
[1, 2, 3].count(&:even?)

hash = { 1 => 2 } #: Hash[Integer, Integer]
hash.transform_keys!({ 1 => 3 }, &:itself)
test.rb:1:14: [error] Cannot pass a value of type `::Proc` as a block-pass-argument of type `(^(::Integer) -> untyped | nil)`
│   ::Proc <: (^(::Integer) -> untyped | nil)
│     ::Proc <: ^(::Integer) -> untyped
│
│ Diagnostic ID: Ruby::BlockTypeMismatch
│
└ [1, 2, 3].sum(&:to_r)
                ~~~~~~

The same call with a literal block is accepted, and [1, 2, 3].map(&:to_r) is accepted too because Enumerable#map declares 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::BlockTypeMismatch errors, all from sum(&:some_method).

Changes

  • core/array.rbs: split Array#sum and Array#count back into blockless and block-taking overloads.
  • core/hash.rbs: same for the (hash[K, K] replacements) branch of Hash#transform_keys!.
  • test/typecheck/block_pass/: new steep check case that fails on master and 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#initialize was 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 ::Proc against a self-bound block whether it is required or optional. Class#initialize already used ?{ ... } in 4.0.3, so it is not a regression. Both are left alone.

Testing

  • rake typecheck_test — passes, including the new test/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_test has a pre-existing error for Array#rfind when run on Ruby 3.4, present on master too.)
  • rake validate and bin/steep check — pass.

🤖 Generated with Claude Code

`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
mybys marked this pull request as draft August 3, 2026 01:24
@mybys
mybys marked this pull request as ready for review August 3, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant