Instance of (A | B) is neither A nor B

OK, maybe not that direct. With simple types it works as may be expected:

os : Array(String | Int32) = ["abc", 123]

os.each do |o|
  case o
  when String
    puts "String"
  when Int32
    puts "Int32"
  else
    puts "unsupported type #{typeof(o)}"
  end
end

outputs

String
Int32

But for not the most simple example (but maybe more realistic):

alias K = Bytes
alias V = Bytes
alias KV = Tuple(Bytes, Bytes)
alias O = Tuple(K, Nil) |
          Tuple(Nil, V) |
          Tuple(KV, Nil) |
          KV |
          Tuple(KV, KV)

k = "k".to_slice
v = "v".to_slice
kv = {k, v}

os : Array(O) = [{k, nil},
                 {nil, v},
                 {kv, nil},
                 {k, v},
                 {kv, kv}]

os.each do |o|
  case o
  when Tuple(K, Nil)
    puts "Tuple(K, Nil)"
  when Tuple(Nil, K)
    puts "Tuple(Nil, K)"
  when Tuple(KV, Nil)
    puts "Tuple(KV, Nil)"
  when KV
    puts "KV"
  when Tuple(KV, KV)
    puts "Tuple(KV, KV)"
  else
    puts "unsupported type #{typeof(o)}"
  end
end

it outputs

unsupported type Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil)
unsupported type Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil)
unsupported type Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil)
unsupported type Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil)
unsupported type Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil)

OK, but maybe enum will do? E.g. if I know that o is actually Tuple(K, Nil), I may just cast it to Tuple(K, Nil), can not I? Unfortunately, there is the same problem:

os.first.as Tuple(K, Nil) # Error: can't cast Tuple(Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil, Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil) to Tuple(Slice(UInt8), Nil)
alias K = Bytes
alias V = Bytes
alias KV = Tuple(Bytes, Bytes)
alias O = Tuple(K, Nil) |
          Tuple(Nil, V) |
          Tuple(KV, Nil) |
          KV |
          Tuple(KV, KV)

testbytes = Bytes.new(8, 0)  
test_o = {testbytes, nil} 

puts "test_o is an `O`? #{test_o.is_a?(O)}" 
puts "test_o is a `KV`? #{test_o.is_a?(KV)}" 
puts "testbytes is a `K`? #{testbytes.is_a?(K)}" 

outputs:

test_o is an `O`? true
test_o is a `KV`? false
testbytes is a `K`? true

where things go wrong is with Array(O)

os : Array(O) = [{k, nil},
                 {nil, v},
                 {kv, nil},
                 {k, v},
                 {kv, kv}]

these values for [k, v, kv] are undefined! they haven’t been assigned to anything so your os.each loop is saying - nope, those aren’t valid types for type union O.

hope this helps, cheers

The union of tuples is merging the Tuple’s generic types:

Tuple(
  Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil,
  Slice(UInt8) | Tuple(Slice(UInt8), Slice(UInt8)) | Nil
)

While I’d expect each tuple to be a distinct concrete type (i.e. the O union type):

Tuple(Slice(UInt8), Nil) |
Tuple(Nil, Slice(UInt8)) |
Tuple(Tuple(Slice(UInt8), Slice(UInt8)), Nil) |
Tuple(Slice(UInt8), Slice(UInt8)) |
Tuple(Tuple(Slice(UInt8), Slice(UInt8)), Tuple(Slice(UInt8), Slice(UInt8)))
1 Like

The when-clauses must use tuple literals:

os.each do |o|
  case o
  when {K, Nil}
    puts "Tuple(K, Nil)"
  when {Nil, K}
    puts "Tuple(Nil, K)"
  when {KV, Nil}
    puts "Tuple(KV, Nil)"
  when {Bytes, Bytes}
    puts "KV"
  when {KV, KV}
    puts "Tuple(KV, KV)"
  else
    puts "unsupported type #{typeof(o)}"
  end
end

The compiler will then produce the appropriate element-wise type checks. Note that o itself’s type is not filtered, you might need something like case {a = o[0], b = o[1]}.

Tuple being covariant only means that you cannot use an exhaustive case expression here.