test = Hash(String, Int32 | String).new
test["hello"] = 1
test["hello"] = "test"
pp test
class Test
property modifications = Hash(String, Int32 | String).new
def initialize(@modifications)
end
end
t = Test.new( {"hello" => 1} )
t = Test.new( {"hello" => "a string!"} )
pp t
Error:
Error: instance variable ‘@modifications’ of Test must be Hash(String, Int32 | String), not Hash(String, Int32)
test works as expected, however, if that same kind of Hash is used inside a class, it will raise an exception.
I think the difference is this: in top-level you create a hash and then assign values to keys. In the class however you set a default hash, but then instantiate the class with a different type.
I would suggest typing the vars explicitely, it really helps with debug later. It kinda defeats the purpose of inferrance, but in my experience better safe than sorry.
It’s not the same kind of hash. The hash type, if not given an explicit type, is inferred from its members. If you only have string values it will be a Hash with String as a key type.
To solve your issue use a hash literal like {…} of String => String | Int32
This is similar to what I was thinking. If the actual code is as simple as the example code, this is probably the best approach.
If the actual code is more complicated, you could use a type alias for the hash type you actually want. The neo4j shard defines a Map type which is just mapping string keys to Neo4j-serializable values, defined as a Hash of those types. So when I need to pass one to a query, instead of:
connection.execute(query, { "foo" => "bar" }) # pass "bar" as the `foo` query param
Applying that to the example code, it would look something like:
class Test
alias Modifications = Hash(String, Int32 | String)
property modifications = Modifications.new
def initialize(@modifications : Modifications)
end
end
pp Test.new(Test::Modifications {"hello" => "a string!"})
Alternatively, if you wanted to implicitly coerce the hash, initialize could be defined as:
def initialize(modifications)
@modifications = modifications.as(Modifications)
end
… so that the call site could pass in a plain hash that conforms to those types, but it’d be worth looking at how it would be called throughout the app to make that judgement, I think.