Directives #line améliorées
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .
Problème de champion : https://github.com/dotnet/csharplang/issues/4747
Résumé
Le compilateur applique le mappage défini par les directives #line
aux emplacements de diagnostic et aux points de séquence émis dans le fichier PDB.
Actuellement, seul le numéro de ligne et le chemin d’accès au fichier peuvent être mappés pendant que le caractère de départ est déduit du code source. La proposition consiste à autoriser la spécification d’un mappage d’étendue complète.
Motivation
Les DLL qui génèrent du code source C# (par exemple, ASP.NET Razor) ne peuvent pas produire de mappage de source précis à l’aide de directives #line
. Cela entraîne une dégradation de l’expérience de débogage dans certains cas, car les points de séquence émis dans la base de données PDB ne peuvent pas être mappés à l’emplacement précis dans le code source d’origine.
Par exemple, le code Razor suivant
@page "/"
Time: @DateTime.Now
génère du code comme tel (simplifié) :
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
La directive ci-dessus associe le point de séquence émis par le compilateur pour l'instruction indiquée par _builder.Add(DateTime.Now);
à la ligne 2, mais la colonne est incorrecte (16 au lieu de 7).
Le générateur source Razor génère en fait incorrectement le code suivant :
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
L'intention était de conserver le caractère de départ et cela fonctionne pour le mappage de l'emplacement du diagnostic. Toutefois, cela ne fonctionne pas pour les points de séquence, car la directive #line
s'applique uniquement aux points de séquence qui les suivent. Il n’existe aucun point de séquence au milieu de l’instruction _builder.Add(DateTime.Now);
(les points de séquence ne peuvent être émis qu’aux instructions IL avec la pile d’évaluation vide). La directive #line 2
dans le code ci-dessus n'a donc pas d'effet sur le PDB généré, et le débogueur ne placera pas de point d'arrêt ni ne s'arrêtera sur l'extrait de code @DateTime.Now
dans la page Razor.
Questions abordées par cette proposition : https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526
Conception détaillée
Nous modifions la syntaxe de line_indicator
utilisée dans la directive pp_line
comme suit :
Actuelles :
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
Proposées :
line_indicator
: '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace decimal_digit+ whitespace file_name
| '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace file_name
| decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
Autrement dit, la directive #line
accepterait soit 5 nombres décimaux (ligne de début, caractère de début, ligne de fin, caractère final, décalage de caractère), 4 nombres décimaux (ligne de début, caractère de début, ligne de fin, caractère final) ou un seul (ligne).
Si décalage de caractère n’est pas spécifié, alors sa valeur par défaut est 0 ; sinon, il spécifie le nombre de caractères UTF-16. Le nombre doit être non négatif et inférieur à la longueur de la ligne suivant la directive #line dans le fichier non mappé.
(ligne de début, caractère de début)-(ligne de fin, caractère final) spécifie une étendue dans le fichier mappé. ligne de début et ligne de fin sont des entiers positifs qui spécifient des numéros de ligne. caractère de début, caractère final sont des entiers positifs qui spécifient des nombres de caractères UTF-16. ligne de début, caractère de début, ligne de fin, caractère final sont basés sur 1, ce qui signifie que la première ligne du fichier et le premier caractère UTF-16 sur chaque ligne est le numéro 1.
L'implémentation imposerait une contrainte à ces nombres de manière à ce qu'ils spécifient une étendue source de point de séquence valide :
- ligne de début : 1 se trouve dans la plage [0, 0x20000000) et n’est pas égal à 0xfeefee.
- ligne de fin : 1 se trouve dans la plage [0, 0x20000000) et n’est pas égal à 0xfeefee.
- caractère de début : 1 se trouve dans la plage [0, 0x10000)
- caractère de fin : 1 se trouve dans la plage [0, 0x10000)
- ligne de fin est supérieure ou égale à ligne de début.
- ligne de début est égale à ligne de fin puis caractère final est supérieur à caractère de début.
Notez que les nombres spécifiés dans la syntaxe de directive sont des nombres basés sur 1, mais que les étendues réelles dans la base de données PDB sont de base zéro. D'où les ajustements -1 ci-dessus.
Les plages cartographiées des points de séquence et les emplacements des diagnostics auxquels la directive #line
s’applique sont calculés comme suit.
Laissez d être le nombre de base zéro de la ligne non mappée contenant la directive #line
.
Let span L = (start : (ligne de début - 1, caractère de début - 1), end : (ligne de fin - 1, caractère de fin - 1)) être une étendue de base zéro spécifiée par la directive.
La fonction M qui mappe une position (ligne, caractère) dans l’étendue de la directive #line
dans le fichier source contenant la directive #line à une position mappée (ligne mappée, caractère mappé) est définie comme suit :
M(l, c) =
l == d + 1 => (L.start.line + l – d – 1, L.start.character + max(c – décalage des caractères, 0))
l>d + 1 => (L.start.line + l – d – 1, c)
Les constructions de syntaxe auxquelles les points de séquence sont associés sont déterminées par l’implémentation du compilateur et non couvertes par cette spécification. Le compilateur décide également pour chaque point de séquence de son étendue non mappée. Cette étendue peut partiellement ou entièrement couvrir la construction de syntaxe associée.
Une fois que les étendues non mappées sont déterminées par le compilateur, la fonction M définie ci-dessus est appliquée à leurs positions de début et de fin, à l’exception de la position de fin de tous les points de séquence dans l’étendue de la directive #line dont l’emplacement non mappé se trouve à la ligne d + 1 et le caractère inférieur au décalage de caractères. La position de fin de tous ces points de séquence est L.end.
L’exemple [5.i] montre pourquoi il est nécessaire de fournir la possibilité de spécifier la position de fin de la première étendue de point de séquence.
La définition ci-dessus permet au générateur du code source non mappé d'éviter de devoir connaître précisément quelles constructions de code source du langage C# produisent des points de séquence. Les étendues mappées des points de séquence dans l’étendue de la directive
#line
sont dérivées de la position relative des étendues non mappées correspondantes à la première étendue non mappée.
La spécification du décalage de caractère permet au générateur d’insérer n’importe quel préfixe à ligne unique sur la première ligne. Ce préfixe est généré du code qui n’est pas présent dans le fichier mappé. Ce préfixe inséré affecte la valeur de la première étendue de point de séquence non mappée. Par conséquent, le caractère de début des étendues de point de séquence suivantes doit être décalé de la longueur du préfixe (décalage de caractère). Voir l’exemple [2].
Exemples
Pour plus de clarté, les exemples utilisent spanof('...')
et lineof('...')
pseudo-syntaxe pour exprimer respectivement la position de début de l’étendue mappée et le numéro de ligne de l’extrait de code spécifié.
1. Première étendue et suivantes
Considérons le code suivant, dont les numéros de ligne à base zéro non mappés sont indiqués à droite :
#line (1,10)-(1,15) "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
d = 3 L = (0, 9).. (0, 14)
Il existe 4 intervalles de points de séquence auxquels la directive s’applique avec les intervalles non mappés et mappés suivants : (4, 2)..(4, 5)=> (0, 9)..(0, 14) (4, 6)..(5, 1)=> (0, 15)..(1, 1) (5, 2)..(5, 5)=> (1, 2)..(1, 5) (6, 4)..(6, 7)=> (2, 4)..(2, 7)
2. Décalage de caractère
Razor génère un préfixe _builder.Add(
de longueur 15, y compris deux espaces au début.
Rasoir:
@page "/"
@F(() => 1+1,
() => 2+2
)
C# généré :
#line hidden
void Render()
{
#line spanof('F(...)') 15 "page.razor" // 4
_builder.Add(F(() => 1+1, // 5
() => 2+2 // 6
)); // 7
#line hidden
}
);
}
d = 4 L = (1, 1)..(3,0) character offset = 15
Étendues :
_builder.Add(F(…));
=>F(…)
: (5, 2).. (7, 2) => (1, 1).. (3, 0)1+1
=>1+1
: (5, 23).. (5, 25) => (1, 9).. (1, 11)2+2
=>2+2
: (6, 7).. (6, 9) => (2, 7).. (2, 9)
3. Razor : étendue de ligne unique
Rasoir:
@page "/"
Time: @DateTime.Now
C# généré :
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
4. Razor : étendue à plusieurs lignes
Rasoir:
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
C# généré :
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('JsonToHtml(@"...")') 15 "page.razor"
_builder.Add(JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}"));
#line hidden
}
);
}
5. Razor : constructions de blocs
i. bloc contenant des expressions
Dans cet exemple, l’étendue mappée du premier point de séquence associé à l’instruction IL émise pour l’instruction _builder.Add(Html.Helper(() =>
doit couvrir l’expression entière de Html.Helper(...)
dans le fichier généré a.razor
. Cette opération est obtenue par l’application de la règle [1] à la position de fin du point de séquence.
@Html.Helper(() =>
{
<p>Hello World</p>
@DateTime.Now
})
#line spanof('Html.Helper(() => { ... })') 13 "a.razor"
_builder.Add(Html.Helper(() =>
#line lineof('{') "a.razor"
{
#line spanof('DateTime.Now') 13 "a.razor"
_builder.Add(DateTime.Now);
#line lineof('}') "a.razor"
}
#line hidden
)
ii. bloc contenant des instructions
Utilise un formulaire #line line file
existant depuis un certain temps
a) Razor n’ajoute aucun préfixe, b) {
n’est pas présent dans le fichier généré et il ne peut pas y avoir de point de séquence placé dessus, par conséquent, l’étendue du premier point de séquence non mappé est inconnue de Razor.
Le caractère de départ de Console
dans le fichier généré doit être aligné avec le fichier Razor.
@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. bloc contenant du code de niveau supérieur (@code, @functions)
Utilise un formulaire #line line file
existant depuis
a) Razor n’ajoute aucun préfixe, b) {
n’est pas présent dans le fichier généré et il ne peut pas y avoir de point de séquence placé dessus, par conséquent, l’étendue du premier point de séquence non mappé est inconnue de Razor.
Le caractère de départ de [Parameter]
dans le fichier généré doit être aligné avec le fichier Razor.
@code {
[Parameter]
public int IncrementAmount { get; set; }
}
#line lineof('[') "a.razor"
[Parameter]
public int IncrementAmount { get; set; }
#line hidden
6. Razor : @for
, @foreach
, @while
, @do
, @if
, @switch
, @using
, @try
, @lock
Utilise un formulaire #line line file
existant, car a) Razor n’ajoute aucun préfixe.
b) l’étendue du premier point de séquence non mappé peut ne pas être connue pour Razor (ou ne doit pas avoir besoin de savoir).
Le caractère de départ du mot clé dans le fichier généré doit être aligné avec le fichier Razor.
@for (var i = 0; i < 10; i++)
{
}
@if (condition)
{
}
else
{
}
#line lineof('for') "a.razor"
for (var i = 0; i < 10; i++)
{
}
#line lineof('if') "a.razor"
if (condition)
{
}
else
{
}
#line hidden
C# feature specifications