object-oriented: design of a class hierarchy maintainable for the representation of AST

I need to design a hierarchy of AST-type classes for code introspection (such as the Java element API used during the annotation process). But I'm not sure how to make it easy to maintain and maintain.

My instinct is to model the language as close as possible and make the most of the type system. To do this, I would use inheritance to model the intuitive "is a" relationship between the syntax elements to get depletion controls in change-expressions. That is to say. MethodElements and Constructor ElementThey are Executable elements TypeAliasElements ClassElements and InterfaceElementThey are TypeElements and so on. This leads to a fairly deep class hierarchy.

However, shared behavior does not respect this hierarchy. For example: PackageElements ClassElements and InterfaceElements can enclose other elements. But PackageElements only enclose others PackageElements o TypeElements and only ClassElements ever locked Constructor Elements. Mixed interfaces in combination with delegates for implementation can be used to share behavior between classes. Even so, it becomes complex very quickly with deep class hierarchies and many mixing interfaces.
Should there be a unique interface for elements that can enclose other elements? Or an interface to be able to enclose packages, another interface to be able to enclose types, another to enclose functions, etc. Should there be separate properties for visibility and modality or simply one that contains all applicable modifiers?

The opposite approach (the one chosen by the Java Elements API) is to make the hierarchy flat with less specific interfaces: all classes implement the getEnclosedElements: List function, with those elements that do not contain anything simply by returning an empty list. Similarly, there are no different types of classes and interfaces. They are only grouped under TypeElement with an enum property that specifies what type it really is. Of course, that makes everything much simpler for me, but it's not obvious to the user just from the interface which classes can include what kind of other elements, so users have to read the API documentation with a lot of more often and your code is less secure.

Which approach is preferable here? I am afraid that deep class hierarchies will make the API invisible, difficult to maintain (because many mixins are necessary to share the behavior between similar classes) and annoying to use. For example: the user may want to write a function to print all the members of a ClassElement or Interface. They can not use the Encloses interface because a PackageElement It also encloses elements and can not use the TypeElement base class because that includes TypeAliasElement that never encloses members. There is no interface or base class that includes only InterfaceElement Y ClassElement.