Isn’t String a subtype of Object this making this assignment valid?
In src/djot/signed_token.cr:19:7
19 | @payload = payload
^-------
Error: instance variable '@payload' of Djot::SignedToken must be Hash(String, Object), not Hash(String, String)
struct Test
@payload : Hash(String, Object)
def initialize
@payload = {"test" => "test"}
end
end
This don’t work because you are assigning a Hash(String, String) to a Hash(String, Object). You need to specify the type like this: @payload = {"test" => "test} of String => Object. See Inheritance - Crystal.
An important point as well is that in Crystal currently if you have
class Type
end
class Subtype < Type
end
You have an “is a” relationship such that (Subtype.new).is_a? Type (note: the #is_a? method works on objects of a type, not the type itself).
However, the generic system does not extend that within generic type variables. So you end up with this:
class Generic(T)
end
(Generic(Subtype).new).is_a? Generic(Type) # => false
I think the most relevant reason for this behavior is that it would be harder to implement what you’re expecting (and would make the compiler slower), but this behavior does work once you understand and expect it.
Side note: we as a community need a document somewhere that we can point to explaining how this behavior works and why it works that way. There are discussions about generics and inheritance regularly on the Gitter, here, and in GitHub issues, and I’ve picked up the little I just wrote in those places, but someone who really understands the relevant issues (and can properly use terms with “variance” in them) needs to write (or start) a definitive, clear document somewhere so that people can contribute to and refine it. That way we can have a direct, clear response to questions like this one.