I'm writing a navigation plugin for Resharper, and my situation is that I have a list of IDeclaredElement
that I obtained from doing
var declaredElements = context.GetData(DataConstants.DECLARED_ELEMENTS)
This element is the element that the user has their mouse cursor on.
What I'd like to do is to get the IDeclaredType
of the declared element, including any type parameters it might have, in the case it's a generic type.
The resharper SDK documentation is quite light when it comes to the type system, and doesn't really explain the relationship between the various types at all.
I've hunted around other plugins to try and find examples of this, but have come up empty. I've checked every Util and Extension class there is to see if there's a method somewhere that gives me what I want, but there's nought.
About the only thing I have found was this:
declaredElements.First().GetSuperTypes()
which returns the type hierarchy, excluding the current type. Useful, but not what I'm looking for.
Does anyone have any experience with this API or understand how it works? I'd love an answer that explained the relationships between the types a little more.
My understanding of it, in brief:
- Types with Declared in the name
(IDeclaredElement, IDeclaredType)
appear to refer to physical code elements. - IType appears to be a top level interface for all types, that doesn't correspond to physical code elements
- I'm unclear on the meaning of types with Element in the name
(ITypeElement, IDeclaredElement)
, perhaps it's referring to AST elements.
I'd love some clarification on this.
I've added an issue to get this updated in the documentation: https://github.com/JetBrains/resharper-devguide/issues/4
I'll try and provide a potted explanation here.
The
ITreeNode
type hierarchy defines the abstract syntax tree of your code. This provides a lot of information, but is very low-level - it maps directly to the raw text of the code. It also misses some higher level information. For example, if I want to get all type members for a class declaration, I could walk the AST for the class, and collect all appropriate tree nodes, but then I'd also have to process partial classes, and the AST provides no information for locating other parts of the class. Similarly, if I see the class declarationpublic class Foo : Bar
, I would have to do manual resolution of theBar
base type.The
IDeclaredElement
type hierarchy is essentially the semantic view of the syntax tree. At its simplest level, a declared element is "something that has declarations". This can be a class declaration, or a method declaration, or something not even related to code - HTML elements, CSS classes and even colours and file system paths (this is why it's called "element" - it needs a name that can apply to a lot of different things).For example, CLR types are represented with the
ITypeElement
interface, which derives fromIDeclaredElement
. It provides methods and properties for getting at the declared elements for the target type's methods, properties, constructors, etc. So, it is (almost) possible to provide a semantic view of a CLR source project solely in terms of declared elements. Almost, but not quite.Declared elements have the
GetDeclarations
method that providesIDeclaration
syntax tree nodes that are the declarations for the declared element. Similarly, theIDeclaration
node provides theDeclaredElement
property to be able to get a declared element from a node.Furthermore, ReSharper has a very powerful mechanism called references, that allow a tree node to have an outgoing reference that will resolve to a declared element (it can also fail to resolve, which is an error such as using a method that isn't yet written, or it can resolve to more than one element, such as using a method without qualifying which overload it is). These references can be applied to any node, such as a variable name referring back to the variable declaration, or the
Bar
inpublic class Foo : Bar
having a reference to the declared element ofBar
(from which it's possible to get theIDeclaration
and the source code ofBar
).This provides an impressive set of features - a syntactic view of the code file, a semantic view of the code declarations, and references to join everything together, but this doesn't cover everything. A declared element provides a semantic view of a declared thing, but isn't intended to represent all usage scenarios.
Specifically (looking at CLR types), it can't represent usage of a type as an array, pointer, or closed generic type.
ITypeElement
can provide a semantic view of the classFoo
orBar<T>
, but it can't representFoo[]
, orBar<Quux>
.Declared elements need to be able to model these usage scenarios as base classes, method signatures, etc. In order to do so, the derived declared elements (such as
ITypeElement
) use an additional interface hierarchy to represent this "type system" information. This hierarchy depends on the language being analysed. For CLR types, it's theIType
hierarchy, for JavaScript, it'sIJavaScriptType
.This
IType
is additional information, rather than a replacement for the declared element's semantic view. TheIType
can return a symbol table of all type members, but doesn't provide accessors in the same way thatITypeElement
does. Instead, (and depending on what's being modelled) anIType
is essentially a wrapper around a declared element and an instance ofISubstitution
, which provides substitutions for generic type parameters (an array is represented as theSystem.Array
type, with an underlying element type, which is itself anIType
, as it may be a closed generic, or another array). The substitution can also be the empty substitution, which doesn't substitute anything, allowing for types represented as open generics, or types that aren't generic at all. TheIDeclaredType
interface is anIType
that refers to a declared element.In order to get an
IType
instance, you either need to get one from an existing declared element (method signature, base class, etc.) or by creating it withTypeFactory.CreateType
. You'll most likely need to specify anISubstitution
, too, if it's a generic type. You can also get at a bunch of common, "predefined" types, via:You can use these types to pass into one of the
TypeFactory.CreateType
methods to act as type parameters to theITypeElement
you also pass in.So, the upshot is, we declare a class in source, this gives us
ITreeNode
,IDeclaration
andITypeDeclaration
. We can useIDeclaration
, or resolve references to get the semantic view of this declaration,IDeclaredElement
, withITypeElement
being the derived interface representing the class. CLR based declared elements useIType
to represent type usage, such as base classes, which might need to be a closed generic, or method parameters which might be open generics, or arrays.IDeclaredType
is a type usage that can get us back to a declared element. And types are often internally represented with a declared element and anISubstitution
, which can fill in any generic parameters, or be the ID substitution, for when there are no generic parameters. And finally, you can get anIType
usingTypeFactory.CreateType
or using the properties onPredefinedType
.