I'm working on a completion (intellisense) facility for C# in emacs.
The idea is, if a user types a fragment, then asks for completion via a particular keystroke combination, the completion facility will use .NET reflection to determine the possible completions.
Doing this requires that the type of the thing being completed, be known. If it's a string, there's a known set of possible methods and properties; if it's an Int32, it has a separate set, and so on.
Using semantic, a code lexer/parser package available in emacs, I can locate the variable declarations, and their types. Given that, it's straightforward to use reflection to get the methods and properties on the type, and then present the list of options to the user. (Ok, not quite straightforward to do within emacs, but using the ability to run a powershell process inside emacs, it becomes much easier. I write a custom .NET assembly to do reflection, load it into the powershell, and then elisp running within emacs can send commands to powershell and read responses, via comint. As a result emacs can get the results of reflection quickly.)
The problem arrives when the code uses var
in the declaration of the thing being completed. That means the type is not explicitly specified, and completion won't work.
How can I reliably determine the actual type used, when the variable is declared with the var
keyword? Just to be clear, I don't need to determine it at runtime. I want to determine it at "Design time".
So far I have these ideas:
- compile and invoke:
- extract the declaration statement, eg `var foo = "a string value";`
- concatenate a statement `foo.GetType();`
- dynamically compile the resulting C# fragment it into a new assembly
- load the assembly into a new AppDomain, run the framgment and get the return type.
- unload and discard the assembly
I know how to do all this. But it sounds awfully heavyweight, for each completion request in the editor.
I suppose I don't need a fresh new AppDomain every time. I could re-use a single AppDomain for multiple temporary assemblies, and amortize the cost of setting it up and tearing it down, across multiple completion requests. That's more a tweak of the basic idea.
- compile and inspect IL
Simply compile the declaration into a module, and then inspect the IL, to determine the actual type that was inferred by the compiler. How would this be possible? What would I use to examine the IL?
Any better ideas out there? Comments? suggestions?
EDIT - thinking about this further, compile-and-invoke is not acceptable, because the invoke may have side effects. So the first option must be ruled out.
Also, I think I cannot assume the presence of .NET 4.0.
UPDATE - The correct answer, unmentioned above, but gently pointed out by Eric Lippert, is to implement a full fidelity type inference system. It;s the only way to reliably determine the type of a var at design time. But, it's also not easy to do. Because I suffer no illusions that I want to attempt to build such a thing, I took the shortcut of option 2 - extract the relevant declaration code, and compile it, then inspect the resulting IL.
This actually works, for a fair subset of the completion scenarios.
For example, suppose in the following code fragments, the ? is the position at which the user asks for completion. This works:
var x = "hello there";
x.?
The completion realizes that x is a String, and provides the appropriate options. It does this by generating and then compiling the following source code:
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
...and then inspecting the IL with simple reflection.
This also works:
var x = new XmlDocument();
x.?
The engine adds the appropriate using clauses to the generated source code, so that it compiles properly, and then the IL inspection is the same.
This works, too:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
It just means the IL inspection has to find the type of the third local variable, instead of the first.
And this:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
...which is just one level deeper that the prior example.
But, what doesn't work is completion on any local variable whose initialization depends at any point on an instance member, or local method argument. Like:
var foo = this.InstanceMethod();
foo.?
Nor LINQ syntax.
I'll have to think about how valuable those things are before I consider addressing them via what is definitely a "limited design" (polite word for hack) for completion.
An approach to addressing the issue with dependencies on method arguments or instance methods would be to replace, in the fragment of code that gets generated, compiled and then IL analyzed, the references to those things with "synthetic" local vars of the same type.
Another Update - completion on vars that depend on instance members, now works.
What I did was interrogate the type (via semantic), and then generate synthetic stand-in members for all existing members. For a C# buffer like this:
public class CsharpCompletion
{
private static int PrivateStaticField1 = 17;
string InstanceMethod1(int index)
{
...lots of code here...
return result;
}
public void Run(int count)
{
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
var fff = nnn.?
...more code here...
...the generated code that gets compiled, so that I can learn from the output IL the type of the local var nnn, looks like this:
namespace Nsbwhi0rdami {
class CsharpCompletion {
private static int PrivateStaticField1 = default(int);
string InstanceMethod1(int index) { return default(string); }
void M0zpstti30f4 (int count) {
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
}
}
}
All of the instance and static type members are available in the skeleton code. It compiles successfully. At that point, determining the type of the local var is straightforward via Reflection.
What makes this possible is:
- the ability to run powershell in emacs
- the C# compiler is really fast. On my machine, it takes about 0.5s to compile an in-memory assembly. Not fast enough for between-keystrokes analysis, but fast enough to support the on-demand generation of completion lists.
I haven't looked into LINQ yet.
That will be a much bigger problem because the semantic lexer/parser emacs has for C#, doesn't "do" LINQ.