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 declaration public class Foo : Bar
, I would have to do manual resolution of the Bar
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 from IDeclaredElement
. 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 provides IDeclaration
syntax tree nodes that are the declarations for the declared element. Similarly, the IDeclaration
node provides the DeclaredElement
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
in public class Foo : Bar
having a reference to the declared element of Bar
(from which it's possible to get the IDeclaration
and the source code of Bar
).
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 class Foo
or Bar<T>
, but it can't represent Foo[]
, or Bar<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 the IType
hierarchy, for JavaScript, it's IJavaScriptType
.
This IType
is additional information, rather than a replacement for the declared element's semantic view. The IType
can return a symbol table of all type members, but doesn't provide accessors in the same way that ITypeElement
does. Instead, (and depending on what's being modelled) an IType
is essentially a wrapper around a declared element and an instance of ISubstitution
, which provides substitutions for generic type parameters (an array is represented as the System.Array
type, with an underlying element type, which is itself an IType
, 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. The IDeclaredType
interface is an IType
that refers to a declared element.
Aside: Resolving references actually resolves to a declared element and an ISubstitution
, again, to model generics. When resolving a reference for a method declaration signature, you need to know both that it's an IList<T>
, and what T
is.
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 with TypeFactory.CreateType
. You'll most likely need to specify an ISubstitution
, too, if it's a generic type. You can also get at a bunch of common, "predefined" types, via:
var type = psiModule.GetPredefinedType(context).String;
You can use these types to pass into one of the TypeFactory.CreateType
methods to act as type parameters to the ITypeElement
you also pass in.
So, the upshot is, we declare a class in source, this gives us ITreeNode
, IDeclaration
and ITypeDeclaration
. We can use IDeclaration
, or resolve references to get the semantic view of this declaration, IDeclaredElement
, with ITypeElement
being the derived interface representing the class. CLR based declared elements use IType
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 an ISubstitution
, which can fill in any generic parameters, or be the ID substitution, for when there are no generic parameters. And finally, you can get an IType
using TypeFactory.CreateType
or using the properties on PredefinedType
.