Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Nota
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .
Problema do especialista: https://github.com/dotnet/csharplang/issues/4747
Resumo
O compilador aplica o mapeamento definido pelas diretivas #line às localizações de diagnóstico e aos pontos de sequenciamento emitidos para o PDB.
Atualmente, somente o número de linha e o caminho do arquivo podem ser mapeados enquanto o caractere inicial é inferido do código-fonte. A proposta é permitir a especificação de mapeamento do intervalo completo.
Motivação
As DSLs que geram código-fonte C# (como ASP.NET Razor) atualmente não podem produzir mapeamento preciso de código usando diretivas #line. Isso resulta em uma experiência de depuração menor em alguns casos, pois os pontos sequenciais emitidos no PDB não conseguem mapear a localização exata no código-fonte original.
Por exemplo, o código Razor a seguir
@page "/"
Time: @DateTime.Now
gera um código assim (simplificado):
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
A diretiva acima mapearia o ponto de sequência emitido pelo compilador no comando _builder.Add(DateTime.Now); para a linha 2, mas a coluna estaria incorreta (16 em vez de 7).
O gerador de origem Razor gera incorretamente o seguinte código:
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
A intenção era preservar o caractere inicial, o que funciona para o mapeamento de localização de diagnóstico. No entanto, isso não funciona para pontos de sequência, pois a diretiva #line se aplica apenas aos pontos de sequência que a seguem. Não há nenhum ponto de sequência no meio da instrução _builder.Add(DateTime.Now); (pontos de sequência só podem ser emitidos nas instruções IL que possuam uma pilha de avaliação vazia). A diretiva #line 2 no código acima não tem efeito no PDB gerado, e o depurador não coloca um ponto de interrupção nem para no trecho @DateTime.Now da página do Razor.
Problemas resolvidos por esta proposta: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526
Design detalhado
Alteramos a sintaxe de line_indicator usada na diretiva pp_line da seguinte forma:
Atualmente:
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
Proposta:
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'
;
Ou seja, a diretiva #line aceitaria 5 números decimais (linha inicial, caractere inicial, linha final, caractere final, deslocamento de caractere), 4 números decimais (linha inicial, caractere inicial, linha final, caractere final) ou apenas um (linha).
Se deslocamento de caractere não for especificado, seu valor padrão será 0, caso contrário, ele especifica o número de caracteres UTF-16. O número deve ser não negativo e menor que o comprimento da linha seguindo a diretiva #line no arquivo não mapeado.
(linha de início, caractere de início) - (linha final, caractere final) especifica um segmento no arquivo mapeado. de linha inicial e de linha final são inteiros positivos que especificam números de linha. caractere inicial, caractere final são inteiros positivos que especificam números de caractere UTF-16. de linha inicial, caractere inicial, de linha final, caractere final são baseados em 1, o que significa que a primeira linha do arquivo e o primeiro caractere UTF-16 em cada linha são atribuídos ao número 1.
A implementação restringiria esses números, por isso especificam um intervalo de origem de ponto sequencial válido:
- linha inicial - 1 está dentro do intervalo [0, 0x20000000) e não é igual a 0xfeefee.
- linha final – 1 está dentro do intervalo [0, 0x20000000) e não é igual a 0xfeefee.
- caractere inicial - 1 está dentro do intervalo [0, 0x10000)
- caractere final - 1 está dentro do intervalo [0, 0x10000)
- linha final é maior ou igual a linha inicial.
- linha de início é igual a linha de fim, então caractere final é maior que caractere inicial.
Observe que os números especificados na sintaxe da diretiva são números baseados em 1, mas os intervalos reais no PDB são baseados em zero. Portanto, há os ajustes de -1 acima.
Os intervalos mapeados dos pontos de sequência e os locais de diagnóstico aos quais a diretiva #line se aplica são calculados da seguinte maneira.
Deixe d ser o número baseado em zero da linha não mapeada que contém a diretiva #line.
Deixe o intervalo L = (início: (linha inicial - 1, caractere inicial - 1), fim: (linha final - 1, caractere final - 1)) ser um intervalo baseado em zero especificado pela diretiva.
A função M que mapeia uma posição (linha, caractere) dentro do escopo da diretiva #line no arquivo de origem que contém a diretiva #line para uma posição mapeada (linha mapeada, caractere mapeado) é definida da seguinte maneira:
M(l, c) =
l == d + 1 => (L.start.line + l – d – 1, L.start.character + max(c – deslocamento de caractere, 0))
l>d + 1 => (L.start.line + l – d – 1, c)
Os constructos de sintaxe aos quais os pontos de sequência estão associados são determinados pela implementação do compilador e não cobertos por essa especificação. O compilador também decide, para cada ponto sequencial, seu intervalo não mapeado. Esse intervalo pode abranger parcial ou totalmente o constructo de sintaxe associado.
Depois que o compilador determina os intervalos não mapeados, a função M definida anteriormente é aplicada às suas posições inicial e final, com exceção da posição final de todos os pontos sequenciais dentro do escopo da diretiva #line, cujo local não mapeado está na linha d + 1 e com o caractere menor que o deslocamento de caractere. A posição final de todos esses pontos sequenciais é L.end.
O exemplo [5.i] demonstra por que é necessário fornecer a capacidade de especificar a posição final do primeiro intervalo de pontos de sequência.
A definição acima permite que o gerador do código-fonte não mapeado evite o conhecimento íntimo de quais construções de origem exatas da linguagem C# produzem pontos de sequência. Os intervalos mapeados dos pontos de sequência no escopo da diretiva
#linesão derivados da posição relativa dos intervalos não mapeados correspondentes para o primeiro intervalo não mapeado.
Especificar o deslocamento de caractere permite que o gerador insira qualquer prefixo de linha única na primeira linha. Este prefixo é um código gerado que não está presente no arquivo mapeado. Esse prefixo inserido afeta o valor do primeiro intervalo de ponto de sequência não mapeado. Portanto, o caractere inicial dos intervalos subsequentes de pontos sequenciais precisa ser deslocado conforme comprimento do prefixo (deslocamento de caractere). Veja o exemplo [2].
Exemplos
Para maior clareza, os exemplos usam a pseudo-sintaxe spanof('...') e lineof('...') para expressar a posição inicial do intervalo mapeado e o número da linha, respectivamente, do trecho de código especificado.
1. Intervalo inicial e subsequentes
Considere o seguinte código com números de linha baseados em zero não mapeados listados à direita:
#line (1,10)-(1,15) "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
d = 3 L = (0, 9).. (0, 14)
Há quatro intervalos de pontos sequenciais aos quais a diretiva se aplica, com os seguintes intervalos não mapeados e mapeados: (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. Deslocamento de caractere
O Razor gera o prefixo _builder.Add( com um comprimento de 15 caracteres (incluindo dois espaços à esquerda).
Razor:
@page "/"
@F(() => 1+1,
() => 2+2
)
C# gerado:
#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) deslocamento de caractere = 15
Intervalos:
-
_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: intervalo de linha única
Razor:
@page "/"
Time: @DateTime.Now
C# gerado:
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
4. Razor: intervalo de várias linhas
Razor:
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
C# gerado:
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('JsonToHtml(@"...")') 15 "page.razor"
_builder.Add(JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}"));
#line hidden
}
);
}
5. Razor: construções de bloco
i. bloco que contém expressões
Neste exemplo, o intervalo mapeado do primeiro ponto de sequência associado à instrução IL emitida para a instrução _builder.Add(Html.Helper(() => precisa abranger toda a expressão de Html.Helper(...) no arquivo gerado a.razor. Isso é obtido pela aplicação da regra [1] à posição final do ponto de sequência.
@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. bloco contendo instruções
Usa o formulário #line line file existente desde então
a) Razor não adiciona nenhum prefixo, b) { não está presente no arquivo gerado e não pode haver um ponto de sequência colocado nele, portanto, o intervalo do primeiro ponto de sequência não mapeado é desconhecido para Razor.
O caractere inicial de Console no arquivo gerado deve ser alinhado com o arquivo Razor.
@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. bloco contendo código de nível superior (@code, @functions)
Usa o formulário #line line file existente desde então
a) Razor não adiciona nenhum prefixo, b) { não está presente no arquivo gerado e não pode haver um ponto de sequência colocado nele, portanto, o intervalo do primeiro ponto de sequência não mapeado é desconhecido para Razor.
O caractere inicial de [Parameter] no arquivo gerado deve ser alinhado com o arquivo 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
Usa o formulário #line line file existente, pois a) Razor não adiciona nenhum prefixo.
b) o intervalo do primeiro ponto de sequência não mapeado pode não ser conhecido pelo Razor (ou não precisaria ser conhecido).
O caractere inicial da palavra-chave no arquivo gerado deve ser alinhado com o arquivo 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