Je suis en train de travailler sur un achèvement (intellisense) pour C# dans emacs.
L'idée est que, si un utilisateur tape un fragment, puis demande à l'achèvement par l'intermédiaire d'un particulier, d'une combinaison de touches, l'achèvement de la facilité d'utilisation .NET de la réflexion pour déterminer les complétions possibles.
Cette opération nécessite le type de la chose d'être complétée, à être connue. Si c'est une chaîne, il y a un ensemble de méthodes et de propriétés; si c'est un Int32, il possède un ensemble distinct, et ainsi de suite.
À l'aide de la sémantique, un code lexer/analyseur de paquet disponible dans emacs, je peux localiser les déclarations de variables et leurs types. Compte tenu de cela, il est simple à utiliser la réflexion pour obtenir les méthodes et les propriétés du type, puis présenter la liste des options à l'utilisateur. (Ok, pas tout à fait simple à faire dans emacs, mais à l'aide de la capacité à exécuter un processus powershell à l'intérieur d'emacs, il devient beaucoup plus facile. J'écris une coutume .NET de l'assemblée de la réflexion, de la charger dans le powershell, puis elisp cours d'exécution dans emacs peut envoyer des commandes powershell et lire les réponses, via comint. Comme un résultat emacs peut obtenir les résultats de la réflexion rapidement.)
Le problème arrive lorsque le code utilise var
dans la déclaration de la chose en cours d'achèvement. Cela signifie que le type n'est pas spécifié, et l'achèvement de ne pas travailler.
Comment puis-je déterminer de manière fiable le type réel utilisé lorsque la variable est déclarée avec l' var
mot-clé? Juste pour être clair, je n'ai pas besoin de le déterminer au moment de l'exécution. Je veux déterminer au "moment de la Conception".
Jusqu'à présent j'ai de ces idées:
- de compiler et d'invoquer:
- extrait de la déclaration de la fonction, par exemple, `var foo = "chaîne de valeur";`
- concaténer une déclaration " foo.GetType();`
- compiler dynamiquement le C# résultant fragment dans la nouvelle assemblée
- charger l'assembly dans un nouveau domaine d'application, exécutez la framgment et d'obtenir le type de retour.
- décharger et de jeter l'assemblée
Je ne sais comment faire tout cela. Mais ça sonne terriblement lourd, pour chaque réalisation requête dans l'éditeur.
Je suppose que je n'ai pas besoin d'un nouveau domaine d'application à chaque fois. J'ai pu re-utiliser un seul domaine d'application pour plusieurs temporaire assemblées, et d'amortir le coût de sa mise en et de le déchirer vers le bas, à travers de multiples achèvement des demandes. C'est plus qu'un réglage de l'idée de base.
- de compiler et d'inspecter IL
Simplement compiler la déclaration dans un module, puis vérifiez l'IL, afin de déterminer le type réel qui a été déduite par le compilateur. Comment serait-ce possible? Que serais-je utiliser pour examiner l'-t-IL?
Toutes les meilleures idées? Des commentaires? des suggestions?
EDIT - penser à cette autre, de compiler et d'invoquer n'est pas acceptable, parce que l'appel peut avoir des effets secondaires. Si la première option doit être exclu.
Aussi, je pense que je ne peut pas conclure à la présence d' .NET 4.0.
Mise à JOUR - La bonne réponse, non mentionnés ci-dessus, mais gentiment remarquer par Eric Lippert, est de mettre en œuvre une pleine fidélité type de système d'inférence. Il;s le seul moyen fiable de déterminer le type d'une variable au moment de la conception. Mais, c'est pas aussi facile à faire. Parce que je ne souffrent pas d'illusions que je veux essayer de construire une telle chose, j'ai pris le raccourci de l'option 2 - extrait de la déclaration code et le compiler, puis inspectez la résultante de l'IL.
Cela fonctionne en fait, pour juste un sous-ensemble de l'achèvement des scénarios.
Par exemple, supposons que dans le code suivant fragments, l' ? est la position à laquelle l'utilisateur en fait la demande pour l'achèvement. Ceci fonctionne:
var x = "hello there";
x.?
La réalisation se rend compte que x est une Chaîne, et offre les options appropriées. Pour cela, il génère et puis compiler le code source suivant:
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
...et ensuite d'inspecter le IL par simple réflexion.
Cela fonctionne aussi:
var x = new XmlDocument();
x.?
Le moteur ajoute le à l'aide de clauses pour le code source généré, de sorte qu'il compile correctement, et puis la IL de l'inspection est la même.
Cela fonctionne aussi:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
Cela signifie simplement IL d'inspection doit trouver le type de la troisième variable locale, au lieu de la première.
Et ceci:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
...ce qui est d'un niveau plus profond que l'état de la exemple.
Mais, ce n'est pas le travail est effectué sur toute variable locale dont l'initialisation dépend à un point sur un membre de l'instance, ou d'un argument de méthode. Comme:
var foo = this.InstanceMethod();
foo.?
Ni la syntaxe LINQ.
Je vais devoir réfléchir à la façon de précieuses ces choses sont avant je envisager de lutter contre eux par le biais de ce qui est certainement un "processus de conception" (mot poli pour hack) pour l'achèvement.
Une approche pour résoudre le problème avec des dépendances sur des arguments de méthode ou les méthodes d'instance serait de remplacer, dans le fragment de code qui est généré, compilé et puis IL a analysé, les références à ces choses "synthétique" local vars du même type.
Une autre mise à Jour - l'achèvement des travaux sur vars qui dépendent des membres de l'instance, fonctionne maintenant.
Ce que j'ai fait a été d'interroger le type (via sémantique), puis de générer synthétique doublure membres de tous les membres existants. Pour un C# tampon comme ceci:
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...
...le code généré qui sera compilé, afin que je puisse apprendre à partir de la sortie IL le type de local var nnn, ressemble à ceci:
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);
}
}
}
L'ensemble de l'instance et de la statique des membres du type sont disponibles dans le squelette de code. Il compile correctement. À ce stade, de déterminer le type de local var est simple via la Réflexion.
Ce qui rend cela possible est:
- la capacité à exécuter powershell dans emacs
- le compilateur C# est vraiment rapide. Sur ma machine, il faut environ 0,5 s pour compiler une mémoire de l'assemblée. Pas assez rapide entre les frappes de l'analyse, mais suffisante pour prendre en charge la génération de la demande de l'achèvement des listes.
Je n'ai pas regardé en LINQ encore.
Qui sera le plus gros problème parce que la sémantique lexer/parser emacs a pour C#, ne pas "faire" de LINQ.