New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Autodetect types of empty array/hash/splat arguments properly #9774
Comments
The first two examples are not supposed to work if we go down the route of making generic type arguments invariant by default. (They may still work if autocasting of array and hash literals is supported, see #10188.) The empty The splat-related issues are a duplicate of #9184. |
Informally speaking, if the type of def foo(x : Array(Int32)); end
foo([]) # T = Int32
def bar(x : Array(U)) forall U; end
bar([]) # T = U (undefined constant U)
def baz(x : U, y : Array(U)) forall U; end
baz('a', []) # T = U = Char In fact this is what certain FP languages such as Haskell do to their empty lists, but it is certainly overkill to introduce universally quantified types to Crystal just because of
Any method call with a For the last example in the OP, one way to write it is as follows: class Tree
property children : Array(String | Tree)
def initialize
@children = [] of String | Tree
end
def initialize(*children : String | Tree)
@children = [*children] of String | Tree
end
end If class Tree
property children : Array(String | Tree) = []
def initialize
end
def initialize(*children : String | Tree)
# okay, `Array#concat` accepts covariant `Array` arguments
@children.concat(children)
end
end (The "no overload matches" error isn't really related to type covariance; a splat parameter with a non-splat restriction always requires at least one argument.) |
When passing an array argument into a function, the type is autodetected properly even with vague (union) type parameter:
Same thing for hashes:
However, regardless of whether or not we use union type parameters in the function signature, an attempt to pass empty array or hash without explicitly specifying the type outright fails:
Considering that all the required type information is already available, this is rather annoying.
Another rather similar issue comes with splat arguments. Suppose we have a recursive
Tree
class, with each node being either anotherTree
or aString
, with parameters passed as a splat:.map {|x| x.as(String | Tree)}.to_a
produces anArray(String | Tree)
while retaining type safety (trying to pass a number results in “can't castInt32
to(String | Tree)
” error).When trying to pass an empty splat, however, that approach fails:
Incidentally, when passing to class methods (regardless if you're passing
*children : String | Tree
,children : Array(String | Tree)
, or@children
), it also invariably rejects union subtypes (and empty splats as well):(Also, when using in class property type, it claims that
Int
is not supported in unions.)The text was updated successfully, but these errors were encountered: