Skip to content

Enumerable#include? should receive any object#2849

Open
HoneyryderChuck wants to merge 1 commit intoruby:masterfrom
HoneyryderChuck:patch-2
Open

Enumerable#include? should receive any object#2849
HoneyryderChuck wants to merge 1 commit intoruby:masterfrom
HoneyryderChuck:patch-2

Conversation

@HoneyryderChuck
Copy link
Contributor

this checks only if an object exists in the collection, regardless of type. ruby does not raise an exception if the types don't match.

this checks only if an object exists in the collection, regardless of type. ruby does not raise an exception if the types don't match.
@soutaro
Copy link
Member

soutaro commented Feb 16, 2026

This behavior is intentional. The goal is to detect more type errors arising from a commonly seen pattern like this:

hash = { foo: :bar } #: Hash[Symbol, Symbol]
hash["foo"]          # This is usually a mistake.

I'm open to discuss this issue here to have some conclusion. Do you have any practical examples or real-world scenarios where the proposed one worked better?

(From a type-theoretic perspective, the current implementation (using T) is inconsistent. Having top/untyped is better -- consistent and sound.)

@soutaro soutaro added this to the RBS 4.0 milestone Feb 16, 2026
@sampersand
Copy link
Contributor

It'd be nice if we could have a signature like:

def []: (K) ->V?
      | (untyped) -> nil

And then that way whenever you use x = hash["foo"] or fail; x + 1 you might get an error because x would be bot

@ParadoxV5
Copy link
Contributor

Oh, we do. I opened #1875 a year and a half ago, and that was suggested even before that.

@HoneyryderChuck
Copy link
Contributor Author

@soutaro any place where you don't control where the argument comes from. I caught this while checking one of the many steep errors i still get on the httpx codename.

Fwiw there are other methods where I'd apply the same reasoning, like Hash#key?, and while I'd expect enforcing typing while make some code a bit more convoluted, I don't see the upside in this case.

@soutaro
Copy link
Member

soutaro commented Feb 26, 2026

@sampersand

def []: (K) ->V?
      | (untyped) -> nil

This doesn't work well in static typing. Having top always returns nil, for example.

hash = { foo: 123 }
key = :foo #: top
hash[key]       # => nil

@ParadoxV5
Copy link
Contributor

@sampersand meant this, @soutaro.

def []: ((K) -> V) & ((untyped) -> nil)

@soutaro
Copy link
Member

soutaro commented Feb 26, 2026

@HoneyryderChuck

any place where you don't control where the argument comes from.

Like receiving network request or loading from files? I was assuming those values have untyped usually.
If you can share any part from httpx codebase, it should really help.

@HoneyryderChuck
Copy link
Contributor Author

@soutaro I'm having a hard time finding the example that motivated me to open this PR. Most of the examples I find in the codebase are of values which are extracted from arrays/strings/via-regexps which may be smth or nil, and due to the "or nil", don't pass the check. One could argue that I could "skip if nil" before doing the include? call, but that results in rather tedious checks, specially if the include? calls are in the middle of if expressions.

Neverthesless, I'll leave you with this use case, that you may consider more "real world":

# @rbs
      SEMAPHORE_LIGHTS: Array[:red | :yellow | :green]

SEMAPHORE_LIGHTS = %i[red yellow green]
SEMAPHORE_LIGHTS.include?(:blue)

Output

Cannot pass a value of type `::Symbol` as an argument of type `(:red | :yellow | :green)`
  ::Symbol <: (:red | :yellow | :green)
    ::Symbol <: :red[Ruby::ArgumentTypeMismatch](https://github.com/soutaro/steep/tree/v1.10.0/manual/ruby-diagnostics.md#Ruby::ArgumentTypeMismatch)

I can't find of a sane way of skipping that.

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.

4 participants