Infer tuple types for array literals against pair interface hints like Hash::_Pair - #2253
Open
sferik wants to merge 1 commit into
Open
Infer tuple types for array literals against pair interface hints like Hash::_Pair#2253sferik wants to merge 1 commit into
Hash::_Pair#2253sferik wants to merge 1 commit into
Conversation
rbs 4.1 changed the block types of Array#to_h and Hash#to_h and the array-of-pairs argument of Hash.[] from tuples to the new Hash::_Pair interface (ruby/rbs#2694). Steep only derives tuple hints from hint types that are tuple types themselves, so a pair literal in a to_h block is now inferred as an array and every call of the form to_h { |x| [key, value] } fails with Ruby::BlockBodyTypeMismatch: Cannot allow block body have type `::Array[(::Symbol | ::Integer)]` because declared as type `::Hash::_Pair[::Symbol, ::Integer]` Hash::_Pair is not a tuple type, but it declares the tuple it accepts through its #to_ary method. When the hint contains no tuple type directly, deconstruct interface and intersection hints with try_convert(type, :to_ary) and use the resulting tuple types as hints, so the array literal is inferred as a tuple and the existing tuple validation and fallback machinery applies unchanged. The intersection case arises when the block body hint combines a tuple with the interface, as in ([::Symbol, ::Numeric] & ::Hash::_Pair[::Symbol, ::Numeric]), where flattening finds no tuple because the intersection is opaque to flatten_union. A genuinely mismatched pair body still fails with Ruby::BlockBodyTypeMismatch, and now reports the precise tuple type of the body instead of an array type.
sferik
force-pushed
the
pair-interface-tuple-hint
branch
from
July 30, 2026 13:14
0ed1b58 to
8b24a47
Compare
10 tasks
svyatov
added a commit
to svyatov/sec_id
that referenced
this pull request
Jul 31, 2026
Gemfile.lock is not tracked, so the routine bumps (Rails 8.1.3.1, RuboCop 1.88.2, YARD 0.9.45, and others) appear only in the resolved lockfile. simplecov 1.0 deprecates `add_filter` and the `# :nocov:` token, both of which this repo uses. The spec helper now calls `skip`, and the two unreachable regions in the ActiveModel adapter use `# simplecov:disable` and `# simplecov:enable`. The bump also unblocks simplecov-cobertura 4.0, which requires simplecov ~> 1.0. rbs is held at 4.0.x. Version 4.1 retypes the block return of `Array#to_h` from the tuple `[K, V]` to the `Hash::_Pair[K, V]` interface. Steep 2.0 cannot infer an array literal as a tuple against an interface hint, so `CFI::AttributeSet#to_h` stops type-checking. See soutaro/steep#2253. Three workarounds were rejected. A `#:` type assertion makes `steep stats` report the file as an error, which trips the fail-closed coverage gate. `map { ... }.to_h` and `each_with_object` both type-check. RuboCop reports `Style/MapToHash` and `Style/ReduceToHash` for them, and the fix each cop offers is the block form Steep rejects.
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.
RBS 4.1 changed the block types of
Array#to_handHash#to_h, and the array-of-pairs argument ofHash.[], from tuples to the newHash::_Pair[K, V]interface (ruby/rbs#2694). Steep only derives tuple hints from hint types that are tuple types themselves, so a pair literal like[key, value]in ato_hblock is now inferred as an array and every call of the formto_h { |x| [key, value] }fails withRuby::BlockBodyTypeMismatch:Hash::_Pairis not a tuple type but it declares the tuple it accepts through its#to_arymethod. When the hint contains no tuple type directly, this change deconstructs interface and intersection hints withtry_convert(type, :to_ary)and uses the resulting tuple types as hints. The array literal is then inferred as a tuple and the existing tuple validation and fallback machinery applies unchanged. All three_Paircall sites funnel through the same array-literal hint path, so one change covers them.The intersection case arises when the block body hint combines a tuple with the interface, for example
([::Symbol, ::Numeric] & ::Hash::_Pair[::Symbol, ::Numeric]). Flattening finds no tuple there because the intersection is opaque toflatten_union, but the shape of the intersection still answers#to_arywith a tuple.A genuinely mismatched pair body still fails with
Ruby::BlockBodyTypeMismatch, and the diagnostic improves, reporting the precise tuple type of the body instead of an array type. A regression test covers this.