Casting problems with large number of types

If I don’t cast type type is Fuse::LowLevel::Request, UInt64, (Fuse::Lib::FuseFileInfo | Int32 | LibC::Stat | Slice(UInt8) | String | UInt64 | Nil), which can’t be passed to the target method because of type restrictions.

If I cast I get the error at the bottom.

Code:

case mdata
  # Shouldn't this make mdata available as `{UInt64, String}`?
  when {UInt64, String}
    case name
      when :lookup
        lookup_data = mdata.as({UInt64, String})

Error:

can't cast (
Tuple(UInt64, Fuse::Lib::FuseFileInfo | Int32 | String | UInt64 | Nil) | 
Tuple(UInt64, LibC::Stat | Slice(UInt8) | String | UInt64, Int32 | Int64 | UInt16 | UInt64, Fuse::Lib::FuseFileInfo | String) | 
Tuple(UInt64, String | UInt64, String | UInt16))
to Tuple(UInt64, String)

Code: https://github.com/didactic-drunk/fuse/tree/low_level
See src/fuse/low_level.cr callback_receive_loop
Run using ./test.

You need to do when Tuple(UInt64, String). In your code you are matching against a tuple value, not a tuple type.

If you want to match against a tuple type you need to use a tuple in the case, as explained in https://crystal-lang.org/reference/syntax_and_semantics/case.html (section “Tuple Literal”)

Edit: actually, the “Tuple Literal” stuff is not relevant. {Foo, Bar} is only a tuple type in the type grammar, not in regular code.

If you want to match against a tuple type you need to use a tuple in the case , as explained in case - Crystal (section “Tuple Literal”)

I did follow the book. I don’t see a reference to Tuple(types). Is the book wrong?

Comparing against a type will perform an is_a? check

case {value1, value2}
when {String, Int32}
  # Matches if value1.is_a?(String) && value2.is_a?(Int32)
  # The type of value1 is known to be a String by the compiler,
  # and the type of value2 is known to be an Int32
end

Using the @def.return_type returns a Tuple() to match against. When I tried the Tuple() syntax (returned from return_type) earlier it matched nothing.

Matching {Int64, String} did match. At runtime it matches.

Sorry, what I said is wrong. But you need to use Tuple(...) to match against a tuple type. Using {...} matches against a tuple value.

Matching {Int64, String} did match. At runtime it matches.

So the value you have is actually a tuple that has Int64 as a type in the first position, and String in the second one? Then using as won’t work. You would need to do as(Tuple(Int64.class, String.class)).

I think the confusion happens because I can’t see all of your code, just an incomplete snippet so I can’t try anything.

I can’t see all of your code, just an incomplete snippet so I can’t try anything.

Link was provided above with a shell script and test case. It’s macro heavy and easier to see the output when the macro fails. Which is what I posted.

Shouldn’t when cast the type for me within scope of the when statement?

case mdata
  when {UInt64, String}
     # mdata is auto cast within this block  

Maybe the nested case statements are throwing the compiler off?

As I said before, if you use when {…} you are not matching against a type but against a value. The compiler only casts when X when X is a type, not a value.

Matching using when Tuple(Int32, Int32) doesn’t work.

https://carc.in/#/r/95ov

Thank you for the code snippet! With it I can finally understand the problem.

I think this is undocumented, but when you mix two tuple types of the same length, for example Tuple(A, B) | Tuple(C, D), the type is turned into Tuple(A | C, B | D). That means that matching against Tuple(A, B) or matching against Tuple(C, D) will never match.

Instead, I recommend not using tuple types and using record to represent the types that you want.

My advice is to avoid tuples and named tuples as much as possible.

Code:

case mdata
  when {UInt64, String}
    lookup_data = mdata.as(Tuple(UInt64.class, String.class))
#                       ^ as
    lookup(req, parent_ino: lookup_data[0], name: lookup_data[1])
end

def lookup(req : Request, parent_ino : UInt64, name : String) : Nil

Error:

Error: no overload matches 'Fuse::MidLevel::Handler#lookup' with types


 Fuse::LowLevel::Request, parent_ino: UInt64.class, name: String.class

I’ll give record/struct a try.

My other solution would make a channel with fibers for each data type. Most apps would need nproc to max_net_connecions Fibers. I don’t know if 8-20 * ncpu fibers with dedicated type channels is faster or a single pool of ncpu fibers inside of a giant case statement.