Dynamically castable opaque references

I’m currently working with references to MLIR types which are converted to and from opaque representations (for bindings to languages such as Java or Python). One challenge I have is that when I get back an opaque reference to an MLIR Type I would like to validate that the reference is of the expected type (there are a number of places where a bindings might mistakenly pass an opaque reference to a different value). I currently do this by stuffing an mlir::TypeID into the reference, and validating reference.typeID == mlir::TypeID::get<ExpectedType>().
This strategy works well, but I’m hoping to improve it for APIs which take a supertype of the reference type. Currently, if I have an opaque reference to a mlir::StringAttr that I need to pass to an API that takes a mlir::Attribute I cannot directly unwrap the opaque reference as an mlir::Attribute because reference.typeID != mlir::TypeID::get<mlir::Attribute>. Instead, I need to explicitly unwrap it as a StringAttr first, which is a little clunky.
I’m wondering if there is an API I can use to essentially test whether a TypeID (or some other opaque representation of the “type”) is equal to or a subclass of a given type (performance isn’t a huge concern here). I’ve looked at Casting.h in LLVM and it seems most of those constructs require the From type as a template argument, and there isn’t an intermediate un-templated value I can store in an opaque form and query later.

I don’t think that LLVM/MLIR’s type casting infra is going to help you because (as you’ve noticed) it doesn’t put any constraints on the memory representation of the target type, so there is nothing to “grab” on to.

However, individual type hierarchies do have a defined layout which maybe could be leveraged? An Attribute just contains a pointer to an AttributeStorage, which contains a pointer to an AbstractAttribute. And the AbstractAttribute has a TypeID on it. There is an Attribute::getTypeID() that drills in and gets it.

I may be missing a subtlety, but for a type hierarchy like Attribute, what is missing?

The issue is that if I receive a reference which I expect to be an attribute, but instead is to something which is not an attribute and I attempt reinterpret it as an attribute to get the attribute storage, I’m poking around in arbitrary memory and the behavior is undefined. The goal is to have a type-independent value which I can ask “are you this type” (currently, this is the TypeID but this representation does not support subclasses).
My current plan is to “bless” certain types such as mlir::Attribute and mlir::Type and store an additional mlir::TypeID like so:

class TypeInfo {
  mlir::TypeID actualTypeID;
  mlir::TypeID commonCurrencySuperclassTypeID;

  void assertIsCompatibleWith(mlir::TypeID typeID) {
    assert(actualTypeID == typeID || commonCurrencySuperclassTypeID == typeID);

This will work for most things, but I was hoping for a more general solution. Sounds like there may not be such a more general solution.

In fact, you can accomplish this with just a single ID, by always unwrapping as the common currency superclass and using dyn_cast.