Bagikan melalui


Direktif #line yang disempurnakan

Nota

Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.

Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut dicatat dalam catatan terkait rapat desain bahasa (LDM) .

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

Masalah juara: https://github.com/dotnet/csharplang/issues/4747

Ringkasan

Kompilator menerapkan pemetaan yang ditentukan oleh arahan #line ke lokasi diagnostik dan titik-titik urutan yang dipancarkan ke PDB.

Saat ini hanya nomor baris dan path file yang dapat dipetakan, sementara karakter awal disimpulkan dari kode sumber. Proposal ini untuk memungkinkan menentukan pemetaan rentang penuh.

Motivasi

DLL yang menghasilkan kode sumber C# (seperti ASP.NET Razor) saat ini tidak dapat menghasilkan pemetaan sumber yang tepat menggunakan arahan #line. Ini menghasilkan pengalaman debugging yang menurun kualitasnya dalam beberapa kasus karena titik urutan yang dipancarkan ke PDB tidak dapat memetakan ke lokasi yang tepat dalam kode sumber asli.

Misalnya, kode Razor berikut

@page "/"
Time: @DateTime.Now

menghasilkan kode seperti itu (disederhanakan):

#line hidden
void Render()
{
   _builder.Add("Time:");
#line 2 "page.razor"
   _builder.Add(DateTime.Now);
#line hidden
}

Direktif di atas akan memetakan titik urutan yang dihasilkan oleh kompilator untuk pernyataan _builder.Add(DateTime.Now); ke baris 2, tetapi kolom akan salah (16 bukan 7).

Generator sumber Razor benar-benar salah menghasilkan kode berikut:

#line hidden
void Render()
{
   _builder.Add("Time:");
   _builder.Add(
#line 2 "page.razor"
      DateTime.Now
#line hidden
);
}

Tujuannya adalah untuk mempertahankan karakter awal dan berfungsi untuk pemetaan lokasi diagnostik. Namun, ini tidak berfungsi untuk titik urutan karena arahan #line hanya berlaku untuk titik urutan yang mengikutinya. Tidak ada titik urutan di tengah pernyataan _builder.Add(DateTime.Now); (titik urutan hanya dapat dipancarkan pada instruksi IL dengan tumpukan evaluasi kosong). Direktif #line 2 dalam kode di atas dengan demikian tidak berpengaruh pada PDB yang dihasilkan dan debugger tidak akan menempatkan titik henti atau berhenti pada cuplikan @DateTime.Now di halaman Razor.

Masalah yang ditangani oleh proposal ini: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526

Desain terperinci

Kami mengubah sintaks line_indicator yang digunakan dalam direktif pp_line seperti itu:

Saat Ini

line_indicator
    : decimal_digit+ whitespace file_name
    | decimal_digit+
    | 'default'
    | 'hidden'
    ;

Diusulkan:

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'
    ;

Artinya, direktif #line akan menerima 5 angka desimal (baris mulai, memulai karakter, garis akhir, karakter akhir, karakter offset), 4 angka desimal (memulaibaris , memulai karakter, garis akhir, karakter akhir), atau satu (baris).

Jika karakter offset tidak ditentukan nilai defaultnya adalah 0, jika tidak, itu menentukan jumlah karakter UTF-16. Angka harus non-negatif dan kurang dari panjang baris yang mengikuti direktif #line dalam file yang tidak dipetakan.

(baris mulai, karakter mulai)-(baris akhir, karakter akhir) menentukan rentang dalam file yang dipetakan. garis mulai dan garis akhir adalah bilangan bulat positif yang menentukan nomor baris. karakter awal, karakter akhir adalah bilangan bulat positif yang menentukan nomor karakter UTF-16. baris awal, karakter awal, baris akhir, karakter akhir berbasis 1, artinya baris pertama pada file dan karakter UTF-16 pertama pada setiap baris diberi nomor 1.

Implementasi akan membatasi angka-angka ini sehingga menentukan rentang sumber titik urutan yang valid:

  • garis awal - 1 berada dalam rentang [0, 0x20000000) dan tidak sama dengan 0xfeefee.
  • garis akhir - 1 berada dalam rentang [0, 0x20000000) dan tidak sama dengan 0xfeefee.
  • Karakter awal - 1 berada dalam rentang [0, 0x10000)
  • karakter akhir dari - 1 berada dalam rentang [0, 0x10000)
  • garis akhir lebih besar atau sama dengan garis mulai .
  • garis mulai sama dengan garis akhir maka karakter akhir lebih besar dari karakter awal.

Perhatikan bahwa angka yang ditentukan dalam sintaks direktif adalah angka berbasis 1 tetapi rentang aktual dalam PDB berbasis nol. Oleh karena itu penyesuaian -1 di atas.

Rentang titik urutan yang dipetakan dan lokasi diagnostik yang direktif #line diterapkan pada dihitung dengan cara berikut.

Biarkan dan menjadi nomor berbasis nol dari baris yang tidak dipetakan yang mengandung direktif #line. Biarkan rentang L = (mulai: (garis mulai - 1, karakter mulai - 1), akhir: (garis akhir - 1, karakter akhir - 1)) menjadi rentang berbasis nol yang ditentukan oleh direktif.

Fungsi M yang memetakan posisi (baris, karakter) dalam cakupan arahan #line dalam file sumber yang berisi direktif #line ke posisi yang dipetakan (baris yang dipetakan, karakter yang dipetakan) didefinisikan sebagai berikut:

M(l, c) =

l == d + 1 => (L.start.line + ld – 1, L.start.character + max(ckarakter, 0))

Saya>d + 1 => (L.start.line + ld – 1, c)

Konstruksi sintaks yang terkait dengan titik urutan ditentukan oleh implementasi kompilator dan tidak tercakup dalam spesifikasi ini. Kompiler juga memutuskan rentang yang tidak dipetakan untuk setiap titik urutan. Rentang ini dapat mencakup sebagian atau sepenuhnya konstruksi sintaksis terkait.

Setelah rentang yang tidak dipetakan ditentukan oleh pengkompilasi, fungsi M yang ditentukan di atas diterapkan ke posisi awal dan akhir, dengan pengecualian posisi akhir semua titik urutan dalam cakupan direktif #line yang lokasinya tidak dipetakan berada di baris d + 1 dan karakter kurang dari offset karakter. Posisi akhir dari semua titik urutan ini adalah L.end.

Contoh [5.i] menunjukkan mengapa perlu memberikan kemampuan untuk menentukan posisi akhir rentang titik urutan pertama.

Definisi di atas memungkinkan generator kode sumber yang tidak dipetakan untuk menghindari pengetahuan intim tentang konstruksi sumber yang tepat dari bahasa C# menghasilkan titik urutan. Rentang titik urutan yang dipetakan dalam cakupan arahan #line berasal dari posisi relatif rentang yang tidak dipetakan yang sesuai ke rentang pertama yang tidak dipetakan.

Menentukan offset karakter memungkinkan generator menyisipkan awalan baris tunggal apa pun pada baris pertama. Awalan ini menghasilkan kode yang tidak ada dalam file yang dipetakan. Prefiks yang disisipkan tersebut memengaruhi nilai rentang titik urutan pertama yang tidak dipetakan. Oleh karena itu karakter awal rentang titik urutan berikutnya perlu diimbangi dengan panjang awalan (karakter offset). Lihat contoh [2].

gambar

Contoh

Untuk kejelasan, contoh menggunakan spanof('...') dan lineof('...') sintaks pseudo untuk mengekspresikan posisi awal rentang yang dipetakan dan nomor baris, masing-masing, dari cuplikan kode yang ditentukan.

1. Rentang pertama dan berikutnya

Pertimbangkan kode berikut dengan nomor baris berbasis nol yang tidak dipetakan yang tercantum di sebelah kanan:

#line (1,10)-(1,15) "a" // 3
  A();B(                // 4
);C();                  // 5
    D();                // 6

d = 3 L = (0, 9).. (0, 14)

Ada 4 titik urutan di mana cakupan arahan yang berlaku untuk rentang berikut yang tidak dipetakan dan dipetakan: (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. Offset karakter

Razor menghasilkan prefiks _builder.Add( dengan panjang 15 (termasuk dua spasi di awal).

Pisau cukur:

@page "/"                                  
@F(() => 1+1, 
   () => 2+2
) 

Dihasilkan C#:

#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) karakter offset = 15

Mencakup:

  • _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: Rentang baris tunggal

Pisau cukur:

@page "/"
Time: @DateTime.Now

Dihasilkan C#:

#line hidden
void Render()
{
  _builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
  _builder.Add(DateTime.Now);
#line hidden
);
}

4. Razor: Rentang multibaris

Pisau cukur:

@page "/"                                  
@JsonToHtml(@"
{
  ""key1"": "value1",
  ""key2"": "value2"
}") 

Dihasilkan C#:

#line hidden
void Render()
{
  _builder.Add("Time:");
#line spanof('JsonToHtml(@"...")') 15 "page.razor"
  _builder.Add(JsonToHtml(@"
{
  ""key1"": "value1",
  ""key2"": "value2"
}"));
#line hidden
}
);
}

5. Razor: konstruksi blok

i. blok berisi ekspresi

Dalam contoh ini, rentang yang dipetakan dari titik urutan pertama yang terkait dengan instruksi IL yang dikeluarkan untuk pernyataan _builder.Add(Html.Helper(() => perlu mencakup seluruh ekspresi Html.Helper(...) dalam file yang dihasilkan a.razor. Hal ini dicapai dengan penerapan aturan [1] hingga posisi akhir titik urutan.

@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. blok yang berisi pernyataan

Menggunakan formulir #line line file yang telah ada sejak

a) Razor tidak menambahkan awalan apa pun, b) { tidak ada dalam file yang dihasilkan dan tidak mungkin ada titik urutan yang ditempatkan di atasnya, oleh karena itu rentang titik urutan pertama yang tidak dipetakan tidak diketahui oleh Razor.

Karakter awal Console dalam file yang dihasilkan harus diselaraskan dengan file Razor.

@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
  Console.WriteLine(1);Console.WriteLine(2);
#line hidden
Iii. blok yang berisi kode tingkat atas (@code, @functions)

Menggunakan formulir #line line file yang telah ada sejak

a) Razor tidak menambahkan awalan apa pun, b) { tidak ada dalam file yang dihasilkan dan tidak mungkin ada titik urutan yang ditempatkan di atasnya, oleh karena itu rentang titik urutan pertama yang tidak dipetakan tidak diketahui oleh Razor.

Karakter awal [Parameter] dalam file yang dihasilkan harus diselaraskan dengan file 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

Menggunakan formulir #line line file yang sudah ada karena a) Razor tidak menambahkan awalan apa pun. b) rentang titik urutan pertama yang tidak dipetakan mungkin tidak diketahui oleh Razor (atau seharusnya tidak perlu mengetahuinya).

Karakter awal kata kunci dalam file yang dihasilkan harus diselaraskan dengan file 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