Skip to content

Accept block-pass arguments for optional blocks - #2261

Merged
soutaro merged 1 commit into
masterfrom
claude/steep-fix-review-b9qpec
Aug 13, 2026
Merged

Accept block-pass arguments for optional blocks#2261
soutaro merged 1 commit into
masterfrom
claude/steep-fix-review-b9qpec

Conversation

@soutaro

@soutaro soutaro commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Fixes #1207.

&:sym and &method(:name) fail against an optional block:

test.rb:1:13: [error] Cannot pass a value of type `::Proc` as a block-pass-argument of type `(^(::Integer) -> void | nil)`
│   ::Proc <: (^(::Integer) -> void | nil)
│     ::Proc <: ^(::Integer) -> void
│
│ Diagnostic ID: Ruby::BlockTypeMismatch
│
└ Foo.new.each(&:to_s)
               ~~~~~~

The Symbol#to_proc / Method#to_proc special cases in :block_pass match on AST::Types::Proc, but the hint of a block-pass argument is ^(...) -> ... | nil when the block is optional, so neither runs and the argument keeps the plain ::Proc it converts to.

Fixed by hinting the argument with the block type itself. BlockPassArg builds both, so it exposes proc_type next to node_type, and the union stays where it belongs — the subtyping check that lets nil through for an optional block.

This removes the need for ruby/rbs#3060.

Comment thread lib/steep/type_construction.rb Outdated
Comment thread test/type_construction_test.rb Outdated
`&:sym` and `&method(:name)` failed to type check when the method
declares an optional block:

```
test.rb:1:13: [error] Cannot pass a value of type `::Proc` as a block-pass-argument of type `(^(::Integer) -> void | nil)`
│   ::Proc <: (^(::Integer) -> void | nil)
│     ::Proc <: ^(::Integer) -> void
│
│ Diagnostic ID: Ruby::BlockTypeMismatch
│
└ Foo.new.each(&:to_s)
               ~~~~~~
```

Both are special cased in `:block_pass` so that `Symbol#to_proc` and
`Method#to_proc` are given a proc type built from the expected block
type. The special cases match on `AST::Types::Proc`, but the hint of a
block-pass argument is `^(...) -> ... | nil` when the block is optional,
so neither applied and the argument kept the plain `::Proc` type it
converts to.

Hint the argument with the block type itself instead of the union.
`BlockPassArg` builds both, so it exposes `proc_type` next to
`node_type`, and the union stays where it belongs -- the subtyping check
that lets `nil` through for an optional block.

Fixes #1207

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nx2PCA7ngK5EZkgNtHM5b6
@soutaro
soutaro force-pushed the claude/steep-fix-review-b9qpec branch from 0b6644e to 9970cb5 Compare August 13, 2026 15:06
@soutaro
soutaro merged commit 243debe into master Aug 13, 2026
22 checks passed
@soutaro
soutaro deleted the claude/steep-fix-review-b9qpec branch August 13, 2026 15:48
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.

Symbol#to_proc passed as an optional block argument causes a warning "Cannot pass a value of type ::Proc as a block-pass-argument"

2 participants