Trying to understand compile-time type mismatch

Any ideas how to navigate this error at compile time?

In spec/redis_spec.cr:352:24

 352 | messages = redis.xread(
                        ^----
Error: no overload matches 'Redis::Client#xread', count: Int32, block: Time::Span, streams: Hash(String, String)

Overloads are:
 - Redis::Client#xread(*, count : Int32 | String | Nil = nil, block : Time::Span | ::Nil = nil, streams : Hash(String, String))
 - Redis::Commands::Stream#xread(*, count : Int32 | String | Nil = nil, block : Time::Span | ::Nil = nil, streams : Hash(String, String))

The code it’s referring to is this:

      messages = redis.xread(
        count: 10,
        block: 2.seconds,
        streams: {key => "-"},
      )

It seems to be telling me:

  • count must be Int32 | String | Nil and it is an Int32
  • block must be Time::Span | Nil and it is a Time::Span
  • streams must be a Hash(String, String) and it is a Hash(String, String)

I don’t understand how it’s not matching. If I remove the types I’m not using in this spec to constrain it to the types I am using, I still get an error:

In spec/redis_spec.cr:352:24

 352 | messages = redis.xread(
                        ^----
Error: no overload matches 'Redis::Client#xread', count: Int32, block: Time::Span, streams: Hash(String, String)

Overloads are:
 - Redis::Client#xread(*, count : Int32, block : Time::Span, streams : Hash(String, String))
 - Redis::Commands::Stream#xread(*, count : Int32, block : Time::Span, streams : Hash(String, String))

At first I thought that the compiler might be tripping over the fact that the method is defined in the Commands::Stream mixin which is then overridden in the Client class to constrain the return type, but that wasn’t it — it does the same if I define it directly on Client.

I’m as confused as you are. The compiler error message doesn’t seem to make sense.

Maybe you could try with -Dpreview_overload_order to exclude any shenanigangs with ordering overloads? I don’t expect this to help, but may as well try.

I meet same type issue when i am writing procodile shards, IIRC, this issue come from other place never related to the unexpected error which compiler told you. for my case, i remember, a type issue for a struct, which cause this completely unrelated errors.

It did not, unfortunately. But also, TIL that option exists. That seems handy.

I also ran into the same kind of error working on this PR last night:

➜  crystal-pg git:(use-binary-encoding) ✗ crystal spec spec/pg/encoder_spec.cr -Dpreview_overload_order
Showing last frame. Use --error-trace for full trace.

In spec/pg/encoder_spec.cr:91:48

 91 | params = args.map { |arg| PQ::Param.encode(arg) }
                                                 ^--
Error: expected argument #1 to 'PQ::Param.encode' to be Array(String), not (Array(String) | Array(UUID))

Overloads are:
 - PQ::Param.encode(val : Array(T), into slice : Bytes) forall T
 - PQ::Param.encode(val : Array(T)) forall T
[A BUNCH OF OTHER OVERLOADS GO HERE]

If this is a bug, I can go ahead and file an issue for it, but I have no idea how to minimize a repro for it.

I might be wrong. but by the error I understand the first argument should be a pointer into memory isn’t it ?

Overloads are:
 - Redis::Client#xread(*, count : Int32 | String | Nil = nil, block : Time::Span | ::Nil = nil, streams : Hash(String, String))

Have you rewrite this class maybe recently ?

Redis::Client

No, the * at the beginning denotes that all parameters after it may only be provided via their name, and not postionally.

Ref: Splats and tuples - Crystal

I just noticed, you didn’t put an extra comma at the end ?

messages = redis.xread(
        count: 10,
        block: 2.seconds,
        streams: {key => "-"}, #I MEAN HERE
      )

My bad

I did, but trailing commas in arg lists are optional — it works with or without it. I think it would be a parser error rather than a type-check error if the comma made a difference here. IIRC, type checking happens in semantic passes, which come after parsing.

1 Like