How to dynamic type check in runtime

expected that err match Exception

err = ArgumentError.new("error")

err_types = [Exception, RuntimeError]

if  err_types.any? { |t| err.is_a?(t) }     # Error: unexpected token: "t"
  puts "type match" 
end

is_a? is a compile primitive and only works with a constant value.

At runtime you can use Class#===(other) to check if other is an instance of that class: t === err.

1 Like

What is wrong here? :thinking:

err = ArgumentError.new("error")

err_types = [IO::Error, Base64::Error]

p! IO::Error === err
p! Base64::Error === err
match = err_types.any? { |t| 
  p! t
  t === err 
}
p! match

result:

IO::Error === err # => false
Base64::Error === err # => false
t # => IO::Error
match # => true

This seems like the same issue as `Class#cast` doesn't work correctly on virtual receivers · Issue #13706 · crystal-lang/crystal · GitHub where Class#=== isn’t correctly instantiated for each metaclass

1 Like

Seems like this is a bug, so is there any other way to solve my problem

What is the problem you’re trying to solve tho? Seeing if an exception type is one of set number of exception types? Technically in your example since you have Exception, that would always be true, no matter what other exception types are in err_types. In which case it would be sufficient to just do:

if err.is_a? Exception
 pp "type match"
end

Or worst case, just manually || all the types together :person_shrugging:. Or maybe use case?

case err
when Exception, RuntimeError then pp "type match"
end
1 Like

Look at the code of my first question, what to do if the match type is a variable
err_types = [Exception, RuntimeError]

A simple scenario: the user configures a set of Excepiton types (not hard coded) that need to be intercepted. If an Exception occurs and matches them during the running of the program, the program will retry

I think what @Blacksmoke16 means is why you have the types in an array to begin with. If you’re trying to define error types to rescue, receiving an actual type instead of an array of class objects would be more idiomatic.

For example, notice how I’m passing in a union of all the exception types I want to capture into the capture method call here:

def capture(error_type : T.class) forall T
  yield
rescue ex : T
  puts "CAUGHT! #{ex}"
end

10.times do
  capture(ArgumentError | TypeCastError) do
    if rand < 0.5
      # ArgumentError
      "asdf".to_i
    else
      # TypeCastError
      "asdf".as(String | Int32).as(Int32)
    end
  end
end

This is the reason you received the original compiler error. Types and variables containing references to classes are not parsed the same way in Crystal, so you can’t deal with them the same way as you would in Ruby. If you’re dealing with types, you probably want to handle them as types rather than as variables.

2 Likes

How to constrain T to be a subtype of Exception :blush:

That constraint is already enforced by the rescue clause. For example, if I change the type to ArgumentError | String, I get this error at compile time:

In dynamic_rescue.cr:3:13

 3 | rescue ex : T
                 ^
Error: (ArgumentError | String) is not a subclass of Exception
2 Likes