Add infinity pattern
Add a pattern to Array#cycle that returns an Enumerator that loops infinitely, as it is missing.
Whether it loops infinitely or not depends on the receiver and cannot be isolated by overloading.
[1,2,3].cycle.each {} # Return type should be `bot` since it will never return
[].cycle.each {} # Just return `nil`
@ksss I'm not sure if adding the bot case here. We don't have bot type with IO#gets while it doesn't return when the stream is closed. (I'm curious if having it improves something, and open to have it if it does.)
The key point of this issue is that it's not about bot itself, but about the type argument.
There is no way to write a test case for [1,2,3].cycle despite it having a return value.
assert_send_type(
"() -> Enumerator[untyped, untyped]",
[1,2,3], :cycle
)
# Failure: test_cycle(ArrayInstanceTest):
# Call trace does not match with given method type: #<struct RBS::Test::CallTrace method_name=:cycle, method_call=<RBS::Test::ArgumentsReturn:@arguments: [], @exit_value: #<Enumerator: [1, 2, 3]:cycle>, @exit_type: :return>, block_calls=[], block_given=false>.
# <["[Array#cycle] ReturnTypeError: expected `Enumerator[untyped, untyped]` but returns `#<Enumerator: [1, 2, 3]:cycle>`"]> was expected to be empty.
Another perspective is that infinite loops can be a critical issue for applications. If programmers could determine the possibility of infinite loops from type information, they could avoid these problems. If it is known that there is a possibility of returning bot, programmers can avoid calling each from Enumerator[untyped, bot].
However, whether this applies to IO#gets is unclear.
I still don't agree having the Enumerator[.., bot] case makes sense.
- A method call may not return doesn't mean we should have
botreturn type. - We cannot use the two
Enumeratorunion cases. We cannot decompose them withis_a?, and theeachreturn type will benil | bot, which is technically identical tonil.
OK make sense! Thank you for reviewing.