Code Completion

geriatric edited this page Sep 30, 2012 · 12 revisions

This page is about SharpDevelop 4.x. The architecture has changed substantially in SharpDevelop 5!

This page tries to give an overview of the code completion infrastructure in SharpDevelop. This is mostly information copied together from chats/questions asked by other developers.

Let's start with a 10000 feet overview of the libraries involved:

  • ICSharpCode.NRefactory contains a C# and VB parser, and an Abstract Source Tree to represent parsed code,
  • ICSharpCode.SharpDevelop.Dom is a type system and can resolve
  • ICSharpCode.TextEditor is the text editor used in SharpDevelop 3
  • ICSharpCode.AvalonEdit is the text editor used in SharpDevelop 4
  • ICSharpCode.SharpDevelop.Editor is a set of interfaces that allow AddIns to work independently of the actual text editor in use

Language bindings like CSharpBinding handle the text editor's code completion requests and use ICSharpCode.SharpDevelop.Dom to get the entries that should be displayed.

ICSharpCode.SharpDevelop.Dom

ICSharpCode.SharpDevelop.Dom is a type system. It represents assemblies using the IProjectContent interface (both compiled assemblies and uncompiled projects opened in the IDE). That interface allows access to the classes in the assembly using the IClass, IMethod, IProperty, IEvent, IField etc. interfaces. These follow the structure of .NET assemblies closely.

One special feature is that for partial classes, there is an IClass instance for each separate part, but also an IClass instance that represents the combined class. Calling GetClass or SearchType on the project content will always return the whole class; but if you're going through ICompilationUnit, you can get at a single class part.

Most important interfaces:

  • IProjectContent - represents an assembly or a project
  • IClass - represents a class (or a part of a partial class)
  • IMethod, IProperty, IEvent, IField (common interface: IMember) - represent a class member
  • IEntity - common base class of IClass and IMember
  • IParameter - parameter of a method
  • IReturnType: really should be named "ITypeReference", as it represents a reference to a class that is (usually) dynamically resolved.
  • ICompilationUnit: represents a code file.
  • IUsingScope: represents a namespace or similar scope block that can contain using statements

Thread safety

IClass, IMember etc. all implement IFreezable: once the parser has returned a ICompilationUnit, it and all objects contained in it will be marked as frozen (become immutable). This allows multiple threads in SharpDevelop to access them without worrying about thread safety. So if you have a IClass instance, you can be sure no other thread is modifying it.

IReturnType however is a different story: it is also thread-safe, but it might resolve to a new version of the class every time you call a method on it (in SharpDevelop, the parser runs on a background thread).

IReturnType

Return types don't have a 1-to-1 correspondence with classes: there is only one class System.String, but possible return types are "string", "System.String<]", "System.String" or even "X" when there is a "using X = System.String;". For generic classes, there is only one IClass instance (List<T>), but separate IReturnTypes (List<int>, List<string>, ...). For partial classes, a return type will never point to a part, only to the IClass instance representing the whole class. It is possible to have return types that don't point to any class - either when the reference cannot be resolved (missing class), or when referring to a type parameter. Calling GetMethods() on a return type will also include inherited members.

Proxy Return Types

Return types can be a 'proxy' for other types. For example, the "GetClassReturnType" will fetch the latest version of a class and then defer the real work to its "DefaultReturnType". An "InferredReturnType" may do complex resolving and overload resolution work, and then forward the method call to the resolved return type (its BaseType).

In a sense, return types can be nested in another. Assuming var x = new List<int>(), then the type of x is actually a complex nesting:

  • InferredReturnType: new List<int[]>()
  • ConstructedReturnType: List<int[]>
  • BaseType = SearchClassReturnType List<>
  • DefaultReturnType: System.Collections.Generic.List<T>
  • TypeArgument = ArrayReturnType int[]
  • BaseType = GetClassReturnType System.Array
  • DefaultReturnType: System.Array
  • ElementType = GetClassReturnType System.Int32
  • DefaultReturnType: System.Int32

Some proxy types are merely forwarding (InferredReturnType, SearchClassReturnType, GetClassReturnType); but others have a modifying effect on the type (ConstructedReturnType, ArrayReturnType, common base class: DecoratingReturnType).

Code examining a return type may want to know about these modifying effects. But it cannot simply cast the IReturnType instance, since forwarding proxy types might be "in the way". For this reason, there are cast methods in the IReturnType interface. Forwarding types will just forward the Cast method call, but decorating return types will respond and return themselves.

If the overhead of going through all the forwarding types gets too high (for example, when comparing a return type with lots of other types), it is a good idea to call "GetDirectReturnType()". This will simplify the tree to skip all forwarding types. However, this also means the new IReturnType is tightly bound to the IClass instance. If a new version of the class comes in (e.g. the user edited the file), the direct return type will continue to point to the old version. Using direct return types can cause memory leaks and will confuse the user (his changes don't show up in completion) - so use them only during calculations, don't keep them around!

Using Scopes

using System;

namespace ICSharpCode.SharpDevelop {
   using Something;

   namespace Subnamespace {
       class X { }
   }
}

This will result in this nesting of scopes:

  • IUsingScope: (ICompilationUnit.UsingScope = root scope representing the global namespace)
  • IUsing: System
  • IUsingScope: "ICSharpCode"
  • IUsingScope: "ICSharpCode.SharpDevelop"
  • IUsing: Something
  • IUsingScope: "ICSharpCode.SharpDevelop.Subnamespace"

The UsingScope property of class X will point to "ICSharpCode.SharpDevelop.Subnamespace". This allows the type resolving feature to traverse the tree of using scopes upwards and find all relevant namespaces to search in.