How it's possible that

I have a class constructor with variable called @headers typed as a Hash(String, Array(String) | String), but I got this error when I call it.

Error: instance variable '@headers' of HTTPHeaders must be Hash(String, Array(String) | String), not Hash(String, Array(String))

Why can’t compiler infer my variable with constructor type?

Unrelated to your issue, this type already exists. Probably would be easier to just use https://crystal-lang.org/api/master/HTTP/Headers.html.

The issue you’re seeing is that Hash(String, Array(String) | String) is NOT the same type as Hash(String, Array(String)). You would need to cast your hash to the proper type. For example.

HTTPHeaders.new({"foo" => ["one","two"]} of String => Array(String) | String)

The reasoning is since the ivar should allow both Array(String) and String values, while the hash you’re trying to pass in would not allow plain String values. using of ... tells it that it should also allow String values.

But why?

My var it’s more restrictive than the constructor type.

That’s the issue. The types must exactly match.

If you could assign a hash of type Hash(String, Array(String)) to a variable restricted to Hash(String, Array(String) | String), what would happen if you call #<<(String) on that variable? The variable is restricted to a hash type with Array(String) | String values, so you should be able to add String. The actual object needs to be able to handle such a call, hence the requirement to for exact type matching, no invariants.
An easy way to transform the hash is hash.transform_values { |value| value.as(Array(String) | String) }

I’m too lazy to write additionals of String => Array(String)|String :wink:

You can alias that:
alias Headers = String => Array(String)|String

Será alias Headers = {} of String => Array(String)|String

1 Like

Yeah, I did typed from the top of my head so pardon me for mistakes. But main idea is correct ;)

1 Like

I would probably just use the stdlib’s HTTP::Headers type versus trying to roll your own. Then you could type stuff to HTTP::Headers and use it via HTTP::Headers{"content-type" => "text/css"}.

2 Likes

HTTPHeaders it’s hyperbole. It’s a propietary protocol, but HTTP like.