Comment puis-je déterminer de manière fiable le type d'une variable déclarée à l'aide de var au moment du design?

109

Je travaille sur une installation de complétion (intellisense) pour C # dans emacs.

L'idée est que, si un utilisateur tape un fragment, puis demande la complétion via une combinaison de touches particulière, la fonction de complétion utilisera la réflexion .NET pour déterminer les complétions possibles.

Pour ce faire, il faut que le type de chose en cours de réalisation soit connu. S'il s'agit d'une chaîne, il existe un ensemble connu de méthodes et de propriétés possibles; s'il s'agit d'un Int32, il a un ensemble séparé, et ainsi de suite.

En utilisant sémantique, un package lexer / parser de code disponible dans emacs, je peux localiser les déclarations de variables et leurs types. Compte tenu de cela, il est simple d'utiliser la réflexion pour obtenir les méthodes et les propriétés du type, puis de présenter la liste des options à l'utilisateur. (Ok, ce n'est pas tout à fait simple à faire dans emacs, mais en utilisant la possibilité d'exécuter un processus PowerShell dans emacs , cela devient beaucoup plus facile. J'écris un assembly .NET personnalisé pour faire la réflexion, le charge dans le PowerShell, puis elisp s'exécutant dans emacs peut envoyer des commandes à PowerShell et lire les réponses, via comint. En conséquence, emacs peut obtenir rapidement les résultats de la réflexion.)

Le problème survient lorsque le code utilise vardans la déclaration de la chose en cours de réalisation. Cela signifie que le type n'est pas spécifié explicitement et que la complétion ne fonctionnera pas.

Comment puis-je déterminer de manière fiable le type réel utilisé, lorsque la variable est déclarée avec le varmot-clé? Pour être clair, je n'ai pas besoin de le déterminer au moment de l'exécution. Je veux le déterminer au "Design time".

Jusqu'à présent, j'ai ces idées:

  1. compilez et invoquez:
    • extraire l'instruction de déclaration, par exemple `var foo =" une valeur de chaîne ";`
    • concaténer une instruction `foo.GetType ();`
    • compilez dynamiquement le fragment C # résultant dans un nouvel assembly
    • chargez l'assembly dans un nouvel AppDomain, exécutez le framgment et obtenez le type de retour.
    • décharger et jeter l'ensemble

    Je sais comment faire tout ça. Mais cela semble terriblement lourd, pour chaque demande d'achèvement dans l'éditeur.

    Je suppose que je n'ai pas besoin d'un nouvel AppDomain à chaque fois. Je pourrais réutiliser un seul AppDomain pour plusieurs assemblages temporaires et amortir le coût de sa configuration et de sa suppression, sur plusieurs demandes d'achèvement. C'est plus une modification de l'idée de base.

  2. compiler et inspecter IL

    Compilez simplement la déclaration dans un module, puis inspectez l'IL pour déterminer le type réel qui a été déduit par le compilateur. Comment cela serait-il possible? Qu'est-ce que j'utiliserais pour examiner l'IL?

Y a-t-il de meilleures idées là-bas? Commentaires? suggestions?


EDIT - en y réfléchissant davantage, la compilation et l'invocation n'est pas acceptable, car l'invocation peut avoir des effets secondaires. La première option doit donc être écartée.

De plus, je pense que je ne peux pas supposer la présence de .NET 4.0.


MISE À JOUR - La bonne réponse, non mentionnée ci-dessus, mais gentiment soulignée par Eric Lippert, est de mettre en œuvre un système d'inférence de type de fidélité complète. C'est le seul moyen de déterminer de manière fiable le type d'une variable au moment de la conception. Mais ce n'est pas non plus facile à faire. Parce que je ne me fais aucune illusion sur le fait que je veux essayer de construire une telle chose, j'ai pris le raccourci de l'option 2 - extraire le code de déclaration pertinent, le compiler, puis inspecter l'IL résultant.

Cela fonctionne réellement, pour un sous-ensemble équitable des scénarios d'achèvement.

Par exemple, supposons que dans les fragments de code suivants, le? est la position à laquelle l'utilisateur demande la complétion. Cela marche:

var x = "hello there"; 
x.?

La complétion se rend compte que x est une chaîne et fournit les options appropriées. Pour ce faire, il génère puis compile le code source suivant:

namespace N1 {
  static class dmriiann5he { // randomly-generated class name
    static void M1 () {
       var x = "hello there"; 
    }
  }
}

... puis en inspectant l'IL avec une simple réflexion.

Cela fonctionne également:

var x = new XmlDocument();
x.? 

Le moteur ajoute les clauses using appropriées au code source généré, de sorte qu'il se compile correctement, puis l'inspection IL est la même.

Cela fonctionne aussi:

var x = "hello"; 
var y = x.ToCharArray();    
var z = y.?

Cela signifie simplement que l'inspection IL doit trouver le type de la troisième variable locale, au lieu de la première.

Et ça:

var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
    {
        foo,
        foo.Length.ToString()
    };
var z = fred.Count;
var x = z.?

... qui est juste un niveau plus profond que l'exemple précédent.

Mais ce qui ne fonctionne pas , c'est la complétion sur toute variable locale dont l'initialisation dépend à tout moment d'un membre d'instance ou d'un argument de méthode locale. Comme:

var foo = this.InstanceMethod();
foo.?

Ni la syntaxe LINQ.

Je vais devoir réfléchir à la valeur de ces choses avant d'envisager de les aborder via ce qui est définitivement une "conception limitée" (mot poli pour hack) pour l'achèvement.

Une approche pour résoudre le problème des dépendances sur les arguments de méthode ou les méthodes d'instance serait de remplacer, dans le fragment de code qui est généré, compilé puis analysé par IL, les références à ces choses par des variables locales "synthétiques" du même type.


Une autre mise à jour - la complétion sur les variables qui dépendent des membres de l'instance, fonctionne maintenant.

Ce que j'ai fait, c'est interroger le type (via sémantique), puis générer des membres suppléants synthétiques pour tous les membres existants. Pour un tampon C # comme celui-ci:

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 est compilé, afin que je puisse apprendre à partir de la sortie IL le type de la var locale 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);
      }
  }
}

Tous les membres de type instance et statique sont disponibles dans le code squelette. Il se compile avec succès. À ce stade, la détermination du type de var local est simple via Reflection.

Ce qui rend cela possible est:

  • la possibilité d'exécuter PowerShell dans emacs
  • le compilateur C # est vraiment rapide. Sur ma machine, il faut environ 0,5 seconde pour compiler un assemblage en mémoire. Pas assez rapide pour l'analyse entre les frappes, mais assez rapide pour prendre en charge la génération à la demande de listes d'achèvement.

Je n'ai pas encore examiné LINQ.
Ce sera un problème beaucoup plus important car le lexeur / analyseur sémantique emacs a pour C #, ne "fait" pas LINQ.

Cheeso
la source
4
Le type de foo est déterminé et renseigné par le compilateur via l'inférence de type. Je soupçonne que les mécanismes sont entièrement différents. Peut-être que le moteur d'inférence de type a un crochet? À tout le moins, j'utiliserais «inférence de type» comme balise.
George Mauer
3
Votre technique de création d'un modèle d'objet "faux" qui a tous les types mais aucune des sémantiques des objets réels est bonne. C'est comme ça que j'ai fait IntelliSense pour JScript dans Visual InterDev à l'époque; nous créons une version "fausse" du modèle objet IE qui a toutes les méthodes et types mais aucun des effets secondaires, puis exécutons un petit interpréteur sur le code analysé au moment de la compilation et voyons quel type revient.
Eric Lippert

Réponses:

202

Je peux vous décrire comment nous faisons cela efficacement dans le "vrai" C # IDE.

La première chose que nous faisons est d'exécuter une passe qui analyse uniquement les éléments de "niveau supérieur" dans le code source. Nous sautons tous les corps de méthode. Cela nous permet de créer rapidement une base de données d'informations sur l'espace de noms, les types et les méthodes (et les constructeurs, etc.) dans le code source du programme. L'analyse de chaque ligne de code dans chaque corps de méthode prendrait beaucoup trop de temps si vous essayez de le faire entre les frappes.

Lorsque l'EDI a besoin de déterminer le type d'une expression particulière à l'intérieur d'un corps de méthode, disons que vous avez tapé «foo». et nous devons comprendre quels sont les membres de foo - nous faisons la même chose; nous sautons autant de travail que nous pouvons raisonnablement.

Nous commençons par une passe qui analyse uniquement les déclarations de variables locales dans cette méthode. Lorsque nous exécutons cette passe, nous effectuons un mappage d'une paire de "scope" et "name" vers un "type determiner". Le "type determiner" est un objet qui représente la notion de "je peux déterminer le type de ce local si j'en ai besoin". Déterminer le type de local peut être coûteux et nous voulons donc reporter ce travail si nécessaire.

Nous avons maintenant une base de données construite paresseusement qui peut nous dire le type de chaque local. Donc, revenons à ce "foo". - nous déterminons dans quelle déclaration se trouve l'expression pertinente, puis nous exécutons l'analyseur sémantique uniquement sur cette déclaration. Par exemple, supposons que vous ayez le corps de la méthode:

String x = "hello";
var y = x.ToCharArray();
var z = from foo in y where foo.

et maintenant nous devons comprendre que foo est de type char. Nous construisons une base de données contenant toutes les métadonnées, les méthodes d'extension, les types de code source, etc. Nous construisons une base de données qui a des déterminants de type pour x, y et z. Nous analysons l'énoncé contenant l'expression intéressante. Nous commençons par le transformer syntaxiquement en

var z = y.Where(foo=>foo.

Afin de déterminer le type de foo, nous devons d'abord connaître le type de y. Donc, à ce stade, nous demandons au déterminant de type "quel est le type de y"? Il démarre ensuite un évaluateur d'expression qui analyse x.ToCharArray () et demande "quel est le type de x"? Nous avons un déterminant de type pour celui qui dit "Je dois rechercher" String "dans le contexte actuel". Il n'y a pas de type String dans le type actuel, nous regardons donc dans l'espace de noms. Ce n'est pas là non plus, donc nous regardons dans les directives using et découvrons qu'il existe un "using System" et que System a un type String. OK, c'est donc le type de x.

Nous interrogeons ensuite les métadonnées de System.String pour le type de ToCharArray et il dit que c'est un System.Char []. Super. Nous avons donc un type pour y.

Maintenant, nous demandons "System.Char [] a-t-il une méthode Où?" Non. Nous regardons donc dans les directives d'utilisation; nous avons déjà précalculé une base de données contenant toutes les métadonnées des méthodes d'extension qui pourraient éventuellement être utilisées.

Maintenant, nous disons "OK, il y a dix-huit douzaines de méthodes d'extension nommées Où dans la portée, est-ce que l'une d'elles a un premier paramètre formel dont le type est compatible avec System.Char []?" Nous commençons donc une série de tests de convertibilité. Cependant, les méthodes d'extension Where sont génériques , ce qui signifie que nous devons faire une inférence de type.

J'ai écrit un moteur d'inférence de type spécial qui peut gérer les inférences incomplètes du premier argument à une méthode d'extension. Nous exécutons l'inférence de type et découvrons qu'il existe une méthode Where qui prend un IEnumerable<T>, et que nous pouvons faire une inférence de System.Char [] à IEnumerable<System.Char>, donc T est System.Char.

La signature de cette méthode est Where<T>(this IEnumerable<T> items, Func<T, bool> predicate), et nous savons que T est System.Char. Nous savons également que le premier argument entre parenthèses de la méthode d'extension est un lambda. Nous démarrons donc un inferrer de type d'expression lambda qui dit «le paramètre formel foo est supposé être System.Char», utilisez ce fait lors de l'analyse du reste du lambda.

Nous avons maintenant toutes les informations dont nous avons besoin pour analyser le corps du lambda, qui est "foo". Nous recherchons le type de foo, nous découvrons que selon le classeur lambda c'est System.Char, et nous avons terminé; nous affichons les informations de type pour System.Char.

Et nous faisons tout sauf l'analyse "de haut niveau" entre les frappes . C'est le vrai problème. En fait, écrire toute l'analyse n'est pas difficile; cela le rend suffisamment rapide pour que vous puissiez le faire à une vitesse de frappe qui est la vraie difficulté.

Bonne chance!

Eric Lippert
la source
8
Eric, merci pour la réponse complète. Vous m'avez beaucoup ouvert les yeux. Pour emacs, je n'aspirais pas à produire un moteur dynamique entre les frappes qui concurrencerait Visual Studio en termes de qualité de l'expérience utilisateur. D'une part, en raison de la latence d'environ 0,5 s inhérente à ma conception, la fonction basée sur emacs est et restera uniquement à la demande; pas de suggestions de frappe à l'avance. Pour un autre - je vais implémenter le support de base des variables locales, mais je serai heureux de punt quand les choses deviennent poilues, ou lorsque le graphique de dépendance dépasse une certaine limite. Je ne sais pas encore quelle est cette limite. Merci encore.
Cheeso
13
Honnêtement, cela me stupéfie que tout cela puisse fonctionner si rapidement et de manière fiable, en particulier avec les expressions lambda et l'inférence de type générique. J'étais en fait assez surpris la première fois que j'ai écrit une expression lambda et Intellisense connaissait le type de mon paramètre lorsque j'ai appuyé sur., Même si l'instruction n'était pas encore complète et que je n'ai jamais spécifié explicitement les paramètres génériques des méthodes d'extension. Merci pour ce petit aperçu de la magie.
Dan Bryant
21
@Dan: J'ai vu (ou écrit) le code source et je suis stupéfait que cela fonctionne aussi. :-) Il y a des trucs poilus là-dedans.
Eric Lippert
11
Les gars d'Eclipse le font probablement mieux parce qu'ils sont plus géniaux que le compilateur C # et l'équipe IDE.
Eric Lippert
23
Je ne me souviens pas du tout d'avoir fait ce commentaire stupide. Cela n'a même pas de sens. J'ai dû être ivre. Désolé.
Tomas Andrle
15

Je peux vous dire en gros comment l'EDI Delphi fonctionne avec le compilateur Delphi pour faire de l'intellisense (l'insight code est ce que Delphi l'appelle). Ce n'est pas 100% applicable à C #, mais c'est une approche intéressante qui mérite d'être prise en considération.

La plupart des analyses sémantiques dans Delphi sont effectuées dans l'analyseur lui-même. Les expressions sont tapées au fur et à mesure qu'elles sont analysées, sauf dans les situations où ce n'est pas facile - auquel cas l'analyse par analyse anticipée est utilisée pour déterminer ce qui est prévu, puis cette décision est utilisée dans l'analyse.

L'analyse est en grande partie de descente récursive LL (2), sauf pour les expressions, qui sont analysées en utilisant la priorité des opérateurs. L'une des particularités de Delphi est qu'il s'agit d'un langage à passage unique, donc les constructions doivent être déclarées avant d'être utilisées, donc aucune passe de niveau supérieur n'est nécessaire pour faire ressortir ces informations.

Cette combinaison de fonctionnalités signifie que l'analyseur a à peu près toutes les informations nécessaires à la compréhension du code pour tout point où cela est nécessaire. Voici comment cela fonctionne: l'EDI informe le lexer du compilateur de la position du curseur (le point où la perspicacité du code est souhaitée) et le lexer le transforme en un jeton spécial (il s'appelle le jeton kibitz). Chaque fois que l'analyseur rencontre ce jeton (qui peut être n'importe où), il sait que c'est le signal pour renvoyer toutes les informations dont il dispose à l'éditeur. Il le fait en utilisant un longjmp car il est écrit en C; ce qu'il fait, c'est qu'il informe l'appelant ultime du type de construction syntaxique (c'est-à-dire du contexte grammatical) dans lequel le point de kibitz a été trouvé, ainsi que de toutes les tables symboliques nécessaires pour ce point. Donc par exemple, si le contexte est dans une expression qui est un argument d'une méthode, nous pouvons vérifier les surcharges de méthode, regarder les types d'arguments et filtrer les symboles valides uniquement sur ceux qui peuvent résoudre ce type d'argument (cela réduit beaucoup de cruft non pertinent dans le menu déroulant). Si c'est dans un contexte de portée imbriquée (par exemple après un "."), L'analyseur aura rendu une référence à la portée, et l'EDI peut énumérer tous les symboles trouvés dans cette portée.

D'autres choses sont également faites; par exemple, les corps de méthode sont ignorés si le jeton kibitz ne se trouve pas dans leur plage - ceci est fait de manière optimiste, et annulé s'il a ignoré le jeton. L'équivalent des méthodes d'extension - les assistants de classe en Delphi - ont une sorte de cache versionné, leur recherche est donc raisonnablement rapide. Mais l'inférence de type générique de Delphi est beaucoup plus faible que celle de C #.

Passons maintenant à la question spécifique: inférer les types de variables déclarées avec varéquivaut à la façon dont Pascal infère le type de constantes. Il provient du type de l'expression d'initialisation. Ces types sont construits de bas en haut. Si xest de type Integer, et yest de type Double, alors x + ysera de type Double, car ce sont les règles du langage; etc. Vous suivez ces règles jusqu'à ce que vous ayez un type pour l'expression complète sur le côté droit, et c'est le type que vous utilisez pour le symbole sur la gauche.

Barry Kelly
la source
7

Si vous ne voulez pas avoir à écrire votre propre analyseur pour construire l'arborescence de syntaxe abstraite, vous pouvez envisager d'utiliser les analyseurs de SharpDevelop ou de MonoDevelop , tous deux open source.

Daniel Plaisted
la source
4

Les systèmes Intellisense représentent généralement le code en utilisant une arborescence de syntaxe abstraite, qui leur permet de résoudre le type de retour de la fonction affectée à la variable «var» plus ou moins de la même manière que le compilateur le fera. Si vous utilisez VS Intellisense, vous remarquerez peut-être qu'il ne vous donnera pas le type de var tant que vous n'aurez pas fini de saisir une expression d'affectation valide (résoluble). Si l'expression est toujours ambiguë (par exemple, elle ne peut pas entièrement déduire les arguments génériques de l'expression), le type var ne sera pas résolu. Cela peut être un processus assez complexe, car vous devrez peut-être marcher assez profondément dans un arbre pour résoudre le type. Par exemple:

var items = myList.OfType<Foo>().Select(foo => foo.Bar);

Le type de retour est IEnumerable<Bar>, mais pour résoudre ce problème, il faut savoir:

  1. myList est du type qui implémente IEnumerable .
  2. Il existe une méthode d'extension OfType<T> qui s'applique à IEnumerable.
  3. La valeur résultante est IEnumerable<Foo>et il existe une méthode d'extension Selectqui s'applique à cela.
  4. L'expression lambda foo => foo.Bara le paramètre foo de type Foo. Ceci est déduit par l'utilisation de Select, qui prend un Func<TIn,TOut>et puisque TIn est connu (Foo), le type de foo peut être déduit.
  5. Le type Foo a une propriété Bar, qui est de type Bar. Nous savons que Select renvoie IEnumerable<TOut>et TOut peut être déduit du résultat de l'expression lambda, donc le type d'élément résultant doit être IEnumerable<Bar>.
Dan Bryant
la source
C'est vrai, ça peut devenir assez profond. Je suis à l'aise avec la résolution de toutes les dépendances. En pensant à cela, la première option que j'ai décrite - compiler et invoquer - n'est absolument pas acceptable, car l'invocation de code peut avoir des effets secondaires, comme la mise à jour d'une base de données, et ce n'est pas quelque chose qu'un éditeur devrait faire. La compilation est ok, l'invocation ne l'est pas. En ce qui concerne la construction de l'AST, je ne pense pas que je veuille faire ça. Vraiment, je veux reporter ce travail au compilateur, qui sait déjà comment le faire. Je veux pouvoir demander au compilateur de me dire ce que je veux savoir. Je veux juste une réponse simple.
Cheeso
Le défi de l'inspecter à partir de la compilation est que les dépendances peuvent être arbitrairement profondes, ce qui signifie que vous devrez peut-être tout construire pour que le compilateur génère du code. Si vous faites cela, je pense que vous pouvez utiliser les symboles du débogueur avec l'IL généré et faire correspondre le type de chaque local avec son symbole.
Dan Bryant
1
@Cheeso: le compilateur n'offre pas ce type d'analyse de type en tant que service. J'espère qu'à l'avenir ce sera le cas, mais pas de promesses.
Eric Lippert
oui, je pense que cela pourrait être la voie à suivre - résoudre toutes les dépendances, puis compiler et inspecter IL. @Eric, bon à savoir. Pour l'instant, si je n'aspire pas à faire l'analyse AST complète, je dois donc recourir à un sale hack pour produire ce service en utilisant les outils existants. Par exemple, compilez un fragment de code construit intelligemment, puis utilisez ILDASM (ou similaire) par programme pour obtenir la réponse que je recherche.
Cheeso
4

Puisque vous ciblez Emacs, il peut être préférable de commencer par la suite CEDET. Tous les détails d'Eric Lippert sont déjà couverts dans l'analyseur de code de l'outil CEDET / Semantic pour C ++. Il y a aussi un analyseur C # (qui a probablement besoin d'un peu de TLC) donc les seules parties manquantes sont liées au réglage des parties nécessaires pour C #.

Les comportements de base sont définis dans des algorithmes de base qui dépendent de fonctions surchargeables définies par langue. Le succès du moteur de complétion dépend de combien de réglages ont été effectués. Avec C ++ comme guide, obtenir un support similaire à C ++ ne devrait pas être trop mauvais.

La réponse de Daniel suggère d'utiliser MonoDevelop pour faire l'analyse et l'analyse. Cela pourrait être un mécanisme alternatif au lieu de l'analyseur C # existant, ou il pourrait être utilisé pour augmenter l'analyseur existant.

Eric
la source
Bon, je connais CEDET, et j'utilise le support C # dans le répertoire contrib pour la sémantique. Semantic fournit la liste des variables locales et leurs types. Un moteur de complétion peut analyser cette liste et proposer les bons choix à l'utilisateur. Le problème est lorsque la variable est var. Semantic l'identifie correctement comme var, mais ne fournit pas d'inférence de type. Ma question portait précisément sur la façon de résoudre ce problème . J'ai également cherché à me connecter à l'achèvement CEDET existant, mais je ne savais pas comment. La documentation de CEDET est ... ah ... incomplète.
Cheeso
Commentaire parallèle - CEDET est admirablement ambitieux, mais je l'ai trouvé difficile à utiliser et à étendre. Actuellement, l'analyseur traite "l'espace de noms" comme un indicateur de classe en C #. Je ne pouvais même pas comprendre comment ajouter un "espace de noms" comme élément syntaxique distinct. Cela a empêché toute autre analyse syntaxique, et je ne pouvais pas comprendre pourquoi. J'ai précédemment expliqué la difficulté que j'avais avec le cadre de complétion. Au-delà de ces problèmes, il y a des coutures et des chevauchements entre les pièces. À titre d'exemple, la navigation fait partie à la fois de la sémantique et du sénateur. CEDET semble séduisant, mais en fin de compte ... il est trop compliqué pour s'engager.
Cheeso
Cheeso, si vous voulez tirer le meilleur parti des parties les moins documentées de CEDET, le mieux est d'essayer la liste de diffusion. Il est facile pour les questions d'approfondir des domaines qui n'ont pas encore été bien développés, il faut donc quelques itérations pour trouver de bonnes solutions ou pour expliquer celles qui existent. Pour C # en particulier, puisque je ne sais rien à ce sujet, il n'y aura pas de réponse unique.
Eric
2

C'est un problème difficile à bien faire. Fondamentalement, vous devez modéliser la spécification du langage / compilateur à travers la plupart des lexing / parsing / typechecking et construire un modèle interne du code source que vous pouvez ensuite interroger. Eric le décrit en détail pour C #. Vous pouvez toujours télécharger le code source du compilateur F # (qui fait partie du F # CTP) et jeter un œil àservice.fsi pour voir l'interface exposée hors du compilateur F # que le service de langage F # utilise pour fournir de l'intellisense, des info-bulles pour les types inférés, etc. un sens d'une «interface» possible si vous aviez déjà le compilateur disponible en tant qu'API à appeler.

L'autre méthode consiste à réutiliser les compilateurs tels quels au fur et à mesure que vous les décrivez, puis à utiliser la réflexion ou à examiner le code généré. Ceci est problématique du point de vue que vous avez besoin de `` programmes complets '' pour obtenir une sortie de compilation à partir d'un compilateur, alors que lors de l'édition du code source dans l'éditeur, vous n'avez souvent que des `` programmes partiels '' qui n'analysent pas encore, ne avoir toutes les méthodes encore implémentées, etc.

En bref, je pense que la version «petit budget» est très difficile à bien faire, et la version «réelle» est très, très difficile à bien faire. (Où `` difficile '' mesure ici à la fois `` l'effort '' et la `` difficulté technique ''.)

Brian
la source
Oui, la version "petit budget" a des limites claires. J'essaie de décider ce qu'est «assez bon» et si je peux rencontrer ce bar. D'après ma propre expérience en dogfood de ce que j'ai jusqu'à présent, cela rend l'écriture C # dans emacs beaucoup plus agréable.
Cheeso
0

Pour la solution "1", vous disposez d'une nouvelle fonctionnalité dans .NET 4 pour le faire rapidement et facilement. Donc, si vous pouvez convertir votre programme en .NET 4, ce serait votre meilleur choix.

Softlion
la source