Skip to content

Replace deterministic untyped return types with concrete types - #2966

Merged
soutaro merged 2 commits into
ruby:masterfrom
zonuexe:fix/concrete-untyped-returns
Aug 23, 2026
Merged

Replace deterministic untyped return types with concrete types#2966
soutaro merged 2 commits into
ruby:masterfrom
zonuexe:fix/concrete-untyped-returns

Conversation

@zonuexe

@zonuexe zonuexe commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Several methods were typed -> untyped even though the implementation makes the return value deterministic. This replaces those with concrete types.

Ruby Method untyped Basis
Zlib::GzipFile#sync= boolish setter returns its argument
MonitorMixin::ConditionVariable#wait_for_cond bool C implementation returns Qtrue / Qfalse
MonitorMixin::ConditionVariable#wait bool delegates to #wait_for_cond
MonitorMixin::ConditionVariable#wait_until nil body is an until loop expression
MonitorMixin::ConditionVariable#wait_while nil body is a while loop expression
ERB#run nil print self.result(b)print returns nil
ERB#initialize void constructor
ERB#def_method Symbol module_eval { eval("def …\nend") } returns the method name
ERB::DefMethod.def_erb_method Symbol delegates to ERB#def_method

putc

IO#putc, StringIO#putc, and Zlib::GzipWriter#putc return their argument. They were typed -> untyped (StringIO / GzipWriter) or (Numeric | String) -> (Numeric | String) (IO); this types them as (real) -> real | (String) -> String so the return type tracks the argument. real is also more precise than Numericputc does not accept Complex. (IO#putc was not untyped, but is aligned here for consistency since the others document themselves as "Same as IO".)

Out of scope

  • ObjectSpace.internal_super_of — although the doc says it returns "Class or Module", it actually returns the internal super entry, which can be a T_ICLASS object. That is not a real Module, so untyped is the correct type (the stdlib_test for it confirms this). Left unchanged.
  • The core/class.rbs (allocate, new) and core/struct.rbs (self.new) untypedinstance proposals were left out: Class#new is special-cased by the type checker, and Struct.new(:a, :b) returns a new struct class (not an instance), so instance would be incorrect there.

Test plan

  • bundle exec rbs validate
  • bundle exec rbs -r stringio -r zlib -r monitor -r erb validate
  • bundle exec rake stdlib_test
  • bundle exec rubocop on the changed files

@zonuexe
zonuexe marked this pull request as draft May 21, 2026 10:58
@zonuexe
zonuexe force-pushed the fix/concrete-untyped-returns branch from 0886f54 to 51d27db Compare May 21, 2026 10:59
@zonuexe
zonuexe marked this pull request as ready for review May 21, 2026 11:09
Comment thread stdlib/stringio/0/stringio.rbs Outdated
# See IO#putc.
#
def putc: (Numeric | String arg0) -> untyped
def putc: (Numeric | String arg0) -> (Numeric | String)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about to like this?

Suggested change
def putc: (Numeric | String arg0) -> (Numeric | String)
def putc: (real) -> real
(String) -> String

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I applied it to IO as well.

Comment thread stdlib/zlib/0/gzip_writer.rbs Outdated
# Same as IO.
#
def putc: (Numeric | String arg0) -> untyped
def putc: (Numeric | String arg0) -> (Numeric | String)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@zonuexe
zonuexe force-pushed the fix/concrete-untyped-returns branch from d92b742 to 2292367 Compare May 22, 2026 13:48
@ksss ksss added this to the RBS 4.1 milestone May 23, 2026
@soutaro soutaro modified the milestones: RBS 4.1, RBS 4.2 Jul 27, 2026
Several methods were typed `-> untyped` even though their return value
is determined by the implementation. Give them concrete types:

- `StringIO#putc`, `Zlib::GzipWriter#putc` -> `Numeric | String` (returns the argument)
- `Zlib::GzipFile#sync=` -> `boolish` (setter returns the argument)
- `MonitorMixin::ConditionVariable#wait_for_cond` / `#wait` -> `bool`
- `MonitorMixin::ConditionVariable#wait_until` / `#wait_while` -> `nil` (loop expression)
- `ERB#run` -> `nil`, `ERB#initialize` -> `void`
- `ERB#def_method`, `ERB::DefMethod.def_erb_method` -> `Symbol`
Per review feedback, type `IO#putc`, `StringIO#putc`, and
`Zlib::GzipWriter#putc` as `(real) -> real | (String) -> String`
instead of a flat `(Numeric | String) -> (Numeric | String)`. `putc`
returns its argument, so the overloads preserve the caller's type;
`real` is also more precise than `Numeric` since `putc` does not
accept `Complex`.
@soutaro
soutaro force-pushed the fix/concrete-untyped-returns branch from 2292367 to bdecacf Compare August 23, 2026 13:34
@soutaro
soutaro enabled auto-merge August 23, 2026 13:36
@soutaro
soutaro added this pull request to the merge queue Aug 23, 2026
Merged via the queue into ruby:master with commit 03d4281 Aug 23, 2026
24 checks passed
@zonuexe
zonuexe deleted the fix/concrete-untyped-returns branch August 23, 2026 14:53
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.

3 participants