Skip to content
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

Type computed for block by compiler may not match the actual type. #4252

Closed
RomanHargrave opened this issue Apr 7, 2017 · 3 comments
Closed

Comments

@RomanHargrave
Copy link

RomanHargrave commented Apr 7, 2017

Illustrated on the crystal playground

Code from the example:

# The result of this block is never Nil
result = case str = "string".as(String | Nil)
         when String
            puts "str.class = #{str.class}"
            puts "typeof(str) = #{typeof(str)}"
            str
         when Nil
            "(Nil)"
         end

puts "result.class = #{result.class}"
puts "typeof(result) = #{typeof(result)}"

Output of above:

str.class = String
typeof(str) = String
result.class = String
typeof(result) = (String | Nil)

For a block that never returns String (never Nil), the type of the block is still String | Nil,
which I suspect is because it is returning str which is String | Nil even though it can only ever be
returned when it is String (notice that the output shows the compiler implicity changing the type to String within the match clause).

If it is only possible for the block to return String, the type should not be a union of String and another.

@RomanHargrave
Copy link
Author

NOTE: Adding a default branch (else) causes the compiler to compute the correct type, but the problem I have with that is that the two preexisting branches cover the entire domain of potential types, and the compiler should not expect a default branch in order to determine the type (because the default branch will never be followed...).

@RX14
Copy link
Member

RX14 commented Apr 7, 2017

# The result of this block is never Nil
result = case str = "string".as(String | Nil)
         when String
            puts "str.class = #{str.class}"
            puts "typeof(str) = #{typeof(str)}"
            str
         when Nil
            "(Nil)"
         else
           raise ""
         end

puts "result.class = #{result.class}"
puts "typeof(result) = #{typeof(result)}"
str.class = String
typeof(str) = String
result.class = String
typeof(result) = String

It's because case will return nil if it matches nothing by default.

I don't think the compiler can work out whether a case's branches is exhaustive (at the moment) because it compares using Object#=== which can be overridden. You could make str === Nil be true, and it would segfault. Not sure if this is something @asterite would want to change.

@asterite
Copy link
Member

asterite commented Apr 7, 2017

As @RX14 says, it's about the compiler checking that all types are covered. It's a nice to have, though hard to implement right now.

Closing as duplicate of #1846

@asterite asterite closed this as completed Apr 7, 2017
@RomanHargrave RomanHargrave changed the title Type computer for block by compiler may not match the actual type. Type computed for block by compiler may not match the actual type. Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants