-
Notifications
You must be signed in to change notification settings - Fork 780
Code Completion
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
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 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'll 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
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 so 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).
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"), but separate IReturnTypes ("List", "List", ...). 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.
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.
`{{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.