비고
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련된 언어 디자인 모임(LDM) 노트에 기록되어 있습니다.
C# 언어 표준에 기능 스펙렛을 채택하는 과정에 대해 사양 문서에서 더 자세히 알아볼 수 있습니다.
챔피언 발행호: https://github.com/dotnet/csharplang/issues/4747
요약
컴파일러는 지시문에 정의된 #line 매핑을 PDB에 내보낸 진단 위치 및 시퀀스 지점에 적용합니다.
현재 소스 코드에서 시작 문자가 유추되는 동안에는 줄 번호와 파일 경로만 매핑할 수 있습니다. 제안서에서는 전체 범위 매핑을 지정할 수 있습니다.
동기
C# 소스 코드(예: ASP.NET Razor)를 생성하는 DSL은 현재 지시문을 사용하여 #line 정확한 소스 매핑을 생성할 수 없습니다. 이로 인해 PDB로 내보낸 시퀀스 지점이 원래 소스 코드의 정확한 위치에 매핑할 수 없기 때문에 경우에 따라 디버깅 환경이 저하됩니다.
예를 들어 다음 Razor 코드
@page "/"
Time: @DateTime.Now
는 다음과 같은 코드를 생성합니다(간소화).
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
위의 지시문은 문에 대해 _builder.Add(DateTime.Now); 컴파일러에서 내보낸 시퀀스 지점을 줄 2에 매핑하지만 열은 꺼집니다(7이 아닌 16).
Razor 원본 생성기가 실제로 다음 코드를 잘못 생성합니다.
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
의도는 시작 문자를 유지하는 것이었고 진단 위치 매핑에 대해 작동합니다. 그러나 지시문은 뒤에 오는 시퀀스 요소 #line 에만 적용되므로 시퀀스 포인트에는 작동하지 않습니다. 문 중간에 _builder.Add(DateTime.Now); 시퀀스 지점이 없습니다(시퀀스 지점은 빈 평가 스택이 있는 IL 명령에서만 내보낼 수 있음). 따라서 위의 코드의 지시문은 #line 2 생성된 PDB에 영향을 주지 않으며 디버거는 Razor 페이지의 코드 조각에서 @DateTime.Now 중단점을 배치하거나 중지하지 않습니다.
이 제안에서 해결된 문제: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526
상세 디자인
다음과 같이 지시문에 line_indicator 사용되는 구문을 pp_line 수정합니다.
현재:
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
제안:
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'
;
즉, #line 지시문은 5개의 소수점 숫자(시작 선, 시작 문자, 끝 선, 끝 문자, 문자 오프셋), 4개의 소수점 숫자(시작 선, 시작 문자, 끝 줄, 끝 문자) 또는 단일 숫자(줄)를 허용합니다.
문자 오프셋을 지정하지 않으면 기본값이 0이고, 그렇지 않으면 UTF-16자의 수를 지정합니다. 이 숫자는 음수가 아니고 매핑되지 않은 파일의 #line 지시문 다음에 있는 줄의 길이가 낮아야 합니다.
(시작 줄, 시작 문자)-(끝 선, 끝 문자)는 매핑된 파일의 범위를 지정합니다. 시작 줄과 끝선은 줄 번호를 지정하는 양의 정수입니다. 시작 문자, 끝 문자 는 UTF-16 문자 번호를 지정하는 양의 정수입니다. 시작 줄, 시작 문자, 끝 줄, 끝 문자 는 1부터 시작합니다. 즉, 파일의 첫 번째 줄과 각 줄의 첫 번째 UTF-16 문자에 숫자 1이 할당됩니다.
구현은 유효한 시퀀스 지점 원본 범위를 지정하기 위해 이러한 숫자를 제약합니다.
- 시작 줄 - 1은 [0, 0x20000000) 범위 내에 있으며 0xfeefee 같지 않습니다.
- 끝 줄 - 1은 [0, 0x20000000) 범위 내에 있으며 0xfeefee 같지 않습니다.
- 시작 문자 - 1이 [0, 0x10000) 범위 내에 있습니다.
- 끝 문자 - 1이 [0, 0x10000) 범위 내에 있습니다.
- 끝 선이 시작 선과 크거나 같습니다.
- 시작 줄은 끝선과 같고 끝 문자는 시작 문자보다 큽합니다.
지시문 구문에 지정된 숫자는 1부터 시작하는 숫자이지만 PDB의 실제 범위는 0부터 시작하는 것입니다. 따라서 위의 -1 조정.
시퀀스 지점의 매핑된 범위와 지시문이 #line 적용되는 진단 위치는 다음과 같이 계산됩니다.
지시문을 포함하는 매핑되지 않은 줄의 0부터 시작하는 번호로 설정합니다#line.
범위 L = (start: (start line - 1, start character - 1), end: (end line - 1, end character - 1)) 지시문에 지정된 0부터 시작하는 범위가 됩니다.
매핑된 위치(매핑된 선, 매핑된 문자)에 #line 지시문을 포함하는 소스 파일의 #line 지시문 범위 내에 있는 위치(선, 문자)를 매핑하는 함수 M은 다음과 같이 정의됩니다.
M(l, c) =
l(2) == d + 1 => (L.start.line + l – d – 1, L.start.character + max(c – 문자 오프셋, 0))
l(2)>d + 1 => (L.start.line + l – d – 1, c)
시퀀스 지점이 연결된 구문 구문은 컴파일러 구현에 의해 결정되며 이 사양에서는 다루지 않습니다. 컴파일러는 매핑되지 않은 범위의 각 시퀀스 지점도 결정합니다. 이 범위는 연결된 구문 구문을 부분적으로 또는 완전히 포함할 수 있습니다.
매핑되지 않은 범위가 컴파일러에 의해 결정되면 위에 정의된 함수 M이 시작 및 끝 위치에 적용됩니다. 매핑되지 않은 위치가 줄 d + 1이고 문자 오프셋보다 작은 문자가 있는 #line 지시문 범위 내의 모든 시퀀스 지점의 끝 위치를 제외하고. 이러한 모든 시퀀스 지점의 끝 위치는 L.end입니다.
예제 [5.i]는 첫 번째 시퀀스 지점 범위의 끝 위치를 지정하는 기능을 제공해야 하는 이유를 보여 줍니다.
위의 정의를 사용하면 매핑되지 않은 소스 코드를 생성하여 C# 언어의 정확한 소스 구문이 시퀀스 지점을 생성하는지 잘 알지 못하게 할 수 있습니다. 지시문 범위에 있는 시퀀스 지점의
#line매핑된 범위는 매핑되지 않은 해당 범위의 상대 위치에서 매핑되지 않은 첫 번째 범위로 파생됩니다.
문자 오프셋을 지정하면 생성기에서 첫 번째 줄에 한 줄 접두사를 삽입할 수 있습니다. 이 접두사는 매핑된 파일에 없는 생성된 코드입니다. 이러한 삽입된 접두사는 매핑되지 않은 첫 번째 시퀀스 지점 범위의 값에 영향을 줍니다. 따라서 후속 시퀀스 지점 범위의 시작 문자는 접두사(문자 오프셋)의 길이로 오프셋되어야 합니다. 예제 [2]를 참조하세요.
예시
명확하게 하기 위해 예제에서는 지정된 코드 조각의 매핑된 범위 시작 위치와 줄 번호를 각각 표현하기 위해 사용 및 spanof('...') 의사 구문을 사용합니다lineof('...').
1. 첫 번째 및 후속 범위
오른쪽에 매핑되지 않은 0부터 시작하는 줄 번호가 나열된 다음 코드를 고려합니다.
#line (1,10)-(1,15) "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
d = 3 L = (0, 9). (0, 14)
지시문에 적용되는 시퀀스 지점 범위는 4개이며 매핑되지 않은 범위와 매핑된 범위는 (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. 문자 오프셋
Razor는 길이가 15인 접두사를 생성합니다 _builder.Add( (두 개의 선행 공백 포함).
Razor:
@page "/"
@F(() => 1+1,
() => 2+2
)
생성된 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 = 4L = (1, 1). (3,0) 문자 오프셋 = 15
걸쳐:
-
_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: 한 줄 범위
Razor:
@page "/"
Time: @DateTime.Now
생성된 C#:
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
4. Razor: 여러 줄 범위
Razor:
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
생성된 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: 블록 구문
(i) 식을 포함하는 블록
이 예제에서는 문에 대해 내보내는 IL 명령과 연결된 첫 번째 시퀀스 지점의 매핑된 _builder.Add(Html.Helper(() => 범위가 생성된 파일Html.Helper(...)의 a.razor 전체 식을 포함해야 합니다. 이는 시퀀스 지점의 끝 위치에 규칙 [1]을 적용하여 수행됩니다.
@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. 블록 포함 문
이후 기존 양식을 사용합니다.#line line file
a) Razor는 접두사를 추가하지 않으며, b) { 는 생성된 파일에 존재하지 않으며 시퀀스 지점이 배치될 수 없으므로 매핑되지 않은 첫 번째 시퀀스 지점의 범위를 Razor에 알 수 없습니다.
생성된 파일의 Console 시작 문자는 Razor 파일에 맞춰야 합니다.
@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. 최상위 코드가 포함된 블록(@code, @functions)
이후 기존 양식을 사용합니다.#line line file
a) Razor는 접두사를 추가하지 않으며, b) { 는 생성된 파일에 존재하지 않으며 시퀀스 지점이 배치될 수 없으므로 매핑되지 않은 첫 번째 시퀀스 지점의 범위를 Razor에 알 수 없습니다.
생성된 파일의 [Parameter] 시작 문자는 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
a) Razor는 접두사를 추가하지 않으므로 기존 #line line file 양식을 사용합니다.
b) 매핑되지 않은 첫 번째 시퀀스 지점의 범위는 Razor에 알려지지 않을 수 있습니다(또는 알 필요가 없음).
생성된 파일에서 키워드의 시작 문자는 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