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

Missing error with recursive types and literals #3799

Closed
RX14 opened this issue Dec 29, 2016 · 2 comments
Closed

Missing error with recursive types and literals #3799

RX14 opened this issue Dec 29, 2016 · 2 comments

Comments

@RX14
Copy link
Contributor

RX14 commented Dec 29, 2016

alias RecursiveType = String | 
                      Int32 |
                      Array(RecursiveType) |
                      Hash(String, RecursiveType)

extra_fields = {
  "teste" => "teste",
  "foo" => {
    "bar" => "blabla"
  } of String => RecursiveType,
  "array" => ["teste", "teste"]  of RecursiveType,
  "deep" => [
    {
      "foo" => {
        "foo" => "bar"
      }
    }
  ] of RecursiveType 
} of String => RecursiveType

def deep_print(rc : RecursiveType)
  puts "#{rc} was not a RecursiveType" unless rc.is_a? RecursiveType
  
  case rc
  when Array(RecursiveType)
    rc.each { |itm| deep_print(itm) }
  when Hash(String, RecursiveType)
    rc.each { |key, itm| deep_print(itm) }
  end
end

deep_print(extra_fields)

Expected an error that {"foo" => {"foo" => "bar"}} was not a Hash(String, RecursiveType). Actual:

{"foo" => {"foo" => "bar"}} was not a RecursiveType

https://play.crystal-lang.org/#/r/1i8m

The case statement doesn't match any branches, but I think it would be possible to get a segfault from this somehow.

Crystal 0.20.3

@asterite
Copy link
Member

Yeah, right now type restrictions and is_a? work a bit different, has to do with covariance and contravariance. This can't lead to a segfault because even thought the restriction lets the type pass, the compiler knows that it's not of that type so it won't let you use it as such.

Reduced:

class Foo
end

class Bar < Foo
end

def foo(r : Array(Foo))
  puts r.is_a?(Array(Foo)) # => false
end

foo(Array(Bar).new)

What should happen in the above code? The fundamental question is "is an Array(Bar) an Array(Foo)?". Well, if you want to read from it than the answer is probably yes. But if you want to write to it (if you want to put a Foo inside an Array(Bar)) then the answer is probably now. A type restriction is currently giving you the first answer, while is_a? is giving you the second answer.

Making both give the second answer would be an easy solution, except that things like a restriction being Range(Int, Int) will break.

I'll open a separate issue for this so we can close all related covariance issues and link to that one.

@asterite
Copy link
Member

Closed in favor of #3803

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants