From 8de5d0f07aaa8b7499e54a3ec3cf3f93bcee186c Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Thu, 5 Feb 2026 16:14:48 -0500 Subject: [PATCH 1/6] support enhanced #line directives --- standard/lexical-structure.md | 60 ++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 44b2fe53e..69d471e89 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1494,7 +1494,7 @@ corresponds exactly to the lexical processing of a conditional compilation direc Line directives may be used to alter the line numbers and compilation unit names that are reported by a compiler in output such as warnings and errors. These values are also used by caller-info attributes ([§23.5.6](attributes.md#2356-caller-info-attributes)). -> *Note*: Line directives are most commonly used in meta-programming tools that generate C# source code from some other text input. *end note* +> *Note*: Line directives are most commonly used in meta-programming tools that generate C# source code from some other text input. The information these directives provide might be used for debugging purposes and error handling, by allowing mapping of errors back to the original source file, rather than to the generated intermediate file. *end note* ```ANTLR fragment PP_Line @@ -1506,6 +1506,8 @@ fragment PP_Line_Indicator | Decimal_Digit+ | DEFAULT | 'hidden' + | PP_Start_Line_Character PP_Whitespace? '-' PP_Whitespace? PP_End_Line_Character + PP_Whitespace (PP_Character_Offset PP_Whitespace)? PP_Compilation_Unit_Name ; fragment PP_Compilation_Unit_Name @@ -1516,6 +1518,35 @@ fragment PP_Compilation_Unit_Name_Character // Any Input_Character except " : ~('\u000D' | '\u000A' | '\u0085' | '\u2028' | '\u2029' | '"') ; + +fragment PP_Start_Line_Character + : '(' PP_Whitespace? PP_Start_Line PP_Whitespace? ',' PP_Whitespace? + PP_Start_Character PP_Whitespace? ')' + ; +fragment PP_End_Line_Character + : '(' PP_Whitespace? PP_End_Line PP_Whitespace? ',' PP_Whitespace? + PP_End_Character PP_Whitespace? ')' + ; + +fragment PP_Start_Line + : Decimal_Digit+ + ; + +fragment PP_End_Line + : Decimal_Digit+ + ; + +fragment PP_Start_Character + : Decimal_Digit+ + ; + +fragment PP_End_Character + : Decimal_Digit+ + ; + +fragment PP_Character_Offset + : Decimal_Digit+ + ; ``` When no `#line` directives are present, a compiler reports true line numbers and compilation unit names in its output. When processing a `#line` directive that includes a *PP_Line_Indicator* that is not `default`, a compiler treats the line *after* the directive as having the given line number (and compilation unit name, if specified). @@ -1528,6 +1559,33 @@ A `#line hidden` directive has no effect on the compilation unit and line number > *Note*: Although a *PP_Compilation_Unit_Name* might contain text that looks like an escape sequence, such text is not an escape sequence; in this context a ‘`\`’ character simply designates an ordinary backslash character. *end note* +Together, the tokens *Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. + +*PP_Start_Line_Character* represents the start line (*PP_Start_Line*) and column (*PP_Start_Character*) pair of the first character on the line following the directive, which is the mapped file text; for example, `(1,1)`. + +*PP_End_Line_Character* represents the end line (*PP_End_Line*) and column (*PP_End_Character*) pair of the mapped file text; for example, `(3,10)`. + +*PP_Start_Line* and *PP_End_Line* are positive integers that specify line numbers. *PP_Start_Character* and *PP_End_Character* are positive integers that specify UTF-16 character numbers. All four of these numbers are 1-based, meaning that the first line of the mapped file and the first UTF-16 character on each line is assigned number 1. + +By default, the mapped text starts at the first character on the line following the `#line` directive. However, this can be adjusted using *PP_Character_Offset*. If *PP_Character_Offset* is omitted, it defaults to 0; otherwise, it specifies the number of UTF-16 characters to skip in that next line. That number shall be non-negative and less than the length of the line following the `#line` directive. + +The mapping specified by a *PP_Line* is in scope until the following `#line` directive or the end of the compilation unit, whichever comes first. + +> *Example*: Consider the following: +> +> +> ```csharp +> ??? +> ``` +> +> the output produced might be something like the following: +> +> ```console +> ??? +> ``` +> +> *end example* + ### 6.5.9 Nullable directive The nullable directive controls the nullable context, as described below. From d79d09319dfa8de0880253afa13c8603e6496743 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Thu, 5 Feb 2026 16:30:34 -0500 Subject: [PATCH 2/6] fix md spaces --- standard/lexical-structure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 69d471e89..220f0b831 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1559,7 +1559,7 @@ A `#line hidden` directive has no effect on the compilation unit and line number > *Note*: Although a *PP_Compilation_Unit_Name* might contain text that looks like an escape sequence, such text is not an escape sequence; in this context a ‘`\`’ character simply designates an ordinary backslash character. *end note* -Together, the tokens *Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. +Together, the tokens *Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. *PP_Start_Line_Character* represents the start line (*PP_Start_Line*) and column (*PP_Start_Character*) pair of the first character on the line following the directive, which is the mapped file text; for example, `(1,1)`. @@ -1569,7 +1569,7 @@ Together, the tokens *Start_Line_Character* '-' *PP_End_Line_Character* specify By default, the mapped text starts at the first character on the line following the `#line` directive. However, this can be adjusted using *PP_Character_Offset*. If *PP_Character_Offset* is omitted, it defaults to 0; otherwise, it specifies the number of UTF-16 characters to skip in that next line. That number shall be non-negative and less than the length of the line following the `#line` directive. -The mapping specified by a *PP_Line* is in scope until the following `#line` directive or the end of the compilation unit, whichever comes first. +The mapping specified by a *PP_Line* is in scope until the following `#line` directive or the end of the compilation unit, whichever comes first. > *Example*: Consider the following: > @@ -1577,7 +1577,7 @@ The mapping specified by a *PP_Line* is in scope until the following `#line` dir > ```csharp > ??? > ``` -> +> > the output produced might be something like the following: > > ```console From ff88f283c46e037d50315d7f82b4f4e3c65d3709 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Thu, 5 Feb 2026 16:34:09 -0500 Subject: [PATCH 3/6] fix place-holder example --- standard/lexical-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 220f0b831..97761fd8b 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1575,7 +1575,7 @@ The mapping specified by a *PP_Line* is in scope until the following `#line` dir > > > ```csharp -> ??? +> // exxample goes here > ``` > > the output produced might be something like the following: From ff846a57c6bfd742c98303a74d475aa96f6e201e Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 13 Feb 2026 12:12:01 -0500 Subject: [PATCH 4/6] disable incomplete example extraction --- standard/lexical-structure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 97761fd8b..0a7191757 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1573,9 +1573,9 @@ The mapping specified by a *PP_Line* is in scope until the following `#line` dir > *Example*: Consider the following: > -> +> > ```csharp -> // exxample goes here +> // example goes here > ``` > > the output produced might be something like the following: From f238136a270c01b53ac1358b6646ded5d4bf74d3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 27 Mar 2026 11:42:36 -0400 Subject: [PATCH 5/6] Address open review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced `*Start_Line_Character*` with `*PP_Start_Line_Character*`. - Replaced the placeholder with two illustrative code blocks: 1. Basic span form — `#line (5,3)-(5,17) "template.dsl"` mapping a generated statement back to the original template. 2. Character offset form — same directive with offset `11` to account for an `output.Add(` prefix. - Removed all 3 "UTF-16" qualifiers: - "UTF-16 character numbers" → "character numbers" - "the first UTF-16 character on each line" → "the first character on each line" - "the number of UTF-16 characters to skip" → "the number of characters to skip" --- standard/lexical-structure.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 0a7191757..4e60583e5 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1559,31 +1559,38 @@ A `#line hidden` directive has no effect on the compilation unit and line number > *Note*: Although a *PP_Compilation_Unit_Name* might contain text that looks like an escape sequence, such text is not an escape sequence; in this context a ‘`\`’ character simply designates an ordinary backslash character. *end note* -Together, the tokens *Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. +Together, the tokens *PP_Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. *PP_Start_Line_Character* represents the start line (*PP_Start_Line*) and column (*PP_Start_Character*) pair of the first character on the line following the directive, which is the mapped file text; for example, `(1,1)`. *PP_End_Line_Character* represents the end line (*PP_End_Line*) and column (*PP_End_Character*) pair of the mapped file text; for example, `(3,10)`. -*PP_Start_Line* and *PP_End_Line* are positive integers that specify line numbers. *PP_Start_Character* and *PP_End_Character* are positive integers that specify UTF-16 character numbers. All four of these numbers are 1-based, meaning that the first line of the mapped file and the first UTF-16 character on each line is assigned number 1. +*PP_Start_Line* and *PP_End_Line* are positive integers that specify line numbers. *PP_Start_Character* and *PP_End_Character* are positive integers that specify character numbers. All four of these numbers are 1-based, meaning that the first line of the mapped file and the first character on each line is assigned number 1. -By default, the mapped text starts at the first character on the line following the `#line` directive. However, this can be adjusted using *PP_Character_Offset*. If *PP_Character_Offset* is omitted, it defaults to 0; otherwise, it specifies the number of UTF-16 characters to skip in that next line. That number shall be non-negative and less than the length of the line following the `#line` directive. +By default, the mapped text starts at the first character on the line following the `#line` directive. However, this can be adjusted using *PP_Character_Offset*. If *PP_Character_Offset* is omitted, it defaults to 0; otherwise, it specifies the number of characters to skip in that next line. That number shall be non-negative and less than the length of the line following the `#line` directive. The mapping specified by a *PP_Line* is in scope until the following `#line` directive or the end of the compilation unit, whichever comes first. -> *Example*: Consider the following: +> *Example*: A code generator that produces C# from a template file can use the enhanced form of the `#line` directive to map diagnostics back to positions in the original source. If line 5, columns 3 through 17 of `template.dsl` contains `Greet("Hello")`, the generator might emit: > -> > ```csharp -> // example goes here +> #line (5,3)-(5,17) "template.dsl" +> output.Add(Greet("Hello")); +> #line default > ``` > -> the output produced might be something like the following: +> Any diagnostic produced for the `output.Add(Greet("Hello"));` statement would be reported at line 5, columns 3–17 in `template.dsl`. > -> ```console -> ??? +> The *PP_Character_Offset* form allows the generator to account for an inserted prefix. With a character offset of `11` for the prefix `output.Add(`: +> +> ```csharp +> #line (5,3)-(5,17) 11 "template.dsl" +> output.Add(Greet("Hello")); +> #line default > ``` > +> the offset causes the mapped span to begin at `Greet("Hello")` rather than at `output.Add(`, mapping `Greet("Hello")` to line 5, column 3 in `template.dsl`. +> > *end example* ### 6.5.9 Nullable directive From 2abbfa5aebf1511ecd7cfa65f9bd6ca813b67be9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 27 Mar 2026 11:58:42 -0400 Subject: [PATCH 6/6] review and apply possible omissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Awkward wording in `PP_Start_Line_Character` description **File:** `standard/lexical-structure.md`, line 1564 **What:** The sentence reads: "*PP_Start_Line_Character* represents the start line (*PP_Start_Line*) and column (*PP_Start_Character*) pair of the first character on the line following the directive, which is the mapped file text; for example, `(1,1)`." The clause "which is the mapped file text" is ambiguous — it's unclear whether it modifies "the line following the directive" or "the first character" or the pair itself. The intent (from the feature spec) is that the (line, char) pair specifies a position *in the mapped file*. **Proposed change:** Reword to something like: "*PP_Start_Line_Character* represents a start position in the mapped file, specified as a line (*PP_Start_Line*) and column (*PP_Start_Character*) pair; for example, `(1,1)`. This position corresponds to the first character on the line following the directive in the generated code." ### Missing constraints on span values **File:** `standard/lexical-structure.md`, after line 1568 **What:** The feature spec states ordering constraints on the span values: - *PP_End_Line* shall be greater than or equal to *PP_Start_Line*. - When *PP_Start_Line* equals *PP_End_Line*, *PP_End_Character* shall be greater than *PP_Start_Character*. These ensure the span is well-formed (non-empty, non-inverted). The PR text does not include these constraints. While the existing "The maximum value allowed for `Decimal_Digit+` is implementation-defined" covers upper bounds, the ordering relationship between start and end is a normative requirement that should be stated. **Proposed change:** Add after the paragraph about 1-based numbering (line 1568): "The end position shall not precede the start position: *PP_End_Line* shall be greater than or equal to *PP_Start_Line*, and when *PP_Start_Line* equals *PP_End_Line*, *PP_End_Character* shall be greater than *PP_Start_Character*." ###`§6.5.1` overview bullet for `#line` is now incomplete **File:** `standard/lexical-structure.md`, line 1097 **What:** The overview says: "`#line`, which is used to control line numbers emitted for errors and warnings". With the enhanced form, the directive also controls column/character mapping and source file span information, not just line numbers. **Proposed change:** Update to: "`#line`, which is used to control line numbers and source file mapping emitted for errors and warnings ([§6.5.8](lexical-structure.md#658-line-directives))." Or, more conservatively, leave as-is since "line numbers" is still broadly accurate and the overview is intentionally brief. **Flag for committee discussion.** ### Relationship between enhanced form and existing `#line default` / `#line hidden` descriptions **File:** `standard/lexical-structure.md`, lines 1554–1558 **What:** The existing text says: - "a compiler treats the line *after* the directive as having the given line number (and compilation unit name, if specified)." - "`#line default` directive undoes the effect of all preceding `#line` directives." - "`#line hidden` directive has no effect on the compilation unit and line numbers reported in error messages." The `#line default` description implicitly covers the enhanced form (good). The `#line hidden` description mentions only "line numbers" but not column/span mapping. Since `#line hidden` doesn't affect error reporting at all, this is arguably fine. However, for completeness it could say "source location information" instead. **Proposed change:** Minor — consider updating the `#line hidden` paragraph to say "has no effect on the source location information reported in error messages" instead of "has no effect on the compilation unit and line numbers reported in error messages". ### "mapped file" terminology introduced without definition **File:** `standard/lexical-structure.md`, lines 1562–1566 **What:** The text introduces the phrase "so-called mapped file" and "the mapped file text" and "in the mapped file." This terminology is new to the standard. The existing standard uses "compilation unit name" for the file path specified in `#line`. The term "mapped file" is never formally defined. **Proposed change:** Either define "mapped file" briefly (e.g., "the source file identified by *PP_Compilation_Unit_Name*, referred to as the *mapped file*") or replace with phrasing that refers to *PP_Compilation_Unit_Name* directly. --- standard/lexical-structure.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 4e60583e5..4eabe69e0 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1094,7 +1094,7 @@ The following pre-processing directives are available: - `#define` and `#undef`, which are used to define and undefine, respectively, conditional compilation symbols ([§6.5.4](lexical-structure.md#654-definition-directives)). - `#if`, `#elif`, `#else`, and `#endif`, which are used to skip conditionally sections of source code ([§6.5.5](lexical-structure.md#655-conditional-compilation-directives)). -- `#line`, which is used to control line numbers emitted for errors and warnings ([§6.5.8](lexical-structure.md#658-line-directives)). +- `#line`, which is used to control line numbers and source file mapping emitted for errors and warnings (§6.5.8). - `#error`, which is used to issue errors ([§6.5.6](lexical-structure.md#656-diagnostic-directives)). - `#region` and `#endregion`, which are used to explicitly mark sections of source code ([§6.5.7](lexical-structure.md#657-region-directives)). - `#nullable`, which is used to specify the nullable context ([§6.5.9](lexical-structure.md#659-nullable-directive)). @@ -1555,18 +1555,20 @@ The maximum value allowed for `Decimal_Digit+` is implementation-defined. A `#line default` directive undoes the effect of all preceding `#line` directives. A compiler reports true line information for subsequent lines, precisely as if no `#line` directives had been processed. -A `#line hidden` directive has no effect on the compilation unit and line numbers reported in error messages, or produced by use of `CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)). It is intended to affect source-level debugging tools so that, when debugging, all lines between a `#line hidden` directive and the subsequent `#line` directive (that is not `#line hidden`) have no line number information, and are skipped entirely when stepping through code. +A `#line hidden` directive has no effect on the source location information reported in error messages, or produced by use of `CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)). It is intended to affect source-level debugging tools so that, when debugging, all lines between a `#line hidden` directive and the subsequent `#line` directive (that is not `#line hidden`) have no line number information, and are skipped entirely when stepping through code. > *Note*: Although a *PP_Compilation_Unit_Name* might contain text that looks like an escape sequence, such text is not an escape sequence; in this context a ‘`\`’ character simply designates an ordinary backslash character. *end note* -Together, the tokens *PP_Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the so-called mapped file *PP_Compilation_Unit_Name*. +Together, the tokens *PP_Start_Line_Character* '-' *PP_End_Line_Character* specify a span of characters in the source file identified by *PP_Compilation_Unit_Name* (the ***mapped file***). -*PP_Start_Line_Character* represents the start line (*PP_Start_Line*) and column (*PP_Start_Character*) pair of the first character on the line following the directive, which is the mapped file text; for example, `(1,1)`. +*PP_Start_Line_Character* represents a start position in the mapped file, specified as a line (*PP_Start_Line*) and column (*PP_Start_Character*) pair; for example, `(1,1)`. This position corresponds to the first character on the line following the directive in the generated code. -*PP_End_Line_Character* represents the end line (*PP_End_Line*) and column (*PP_End_Character*) pair of the mapped file text; for example, `(3,10)`. +*PP_End_Line_Character* represents an end position in the mapped file, specified as a line (*PP_End_Line*) and column (*PP_End_Character*) pair; for example, `(3,10)`. *PP_Start_Line* and *PP_End_Line* are positive integers that specify line numbers. *PP_Start_Character* and *PP_End_Character* are positive integers that specify character numbers. All four of these numbers are 1-based, meaning that the first line of the mapped file and the first character on each line is assigned number 1. +The end position shall not precede the start position: *PP_End_Line* shall be greater than or equal to *PP_Start_Line*, and when *PP_Start_Line* equals *PP_End_Line*, *PP_End_Character* shall be greater than *PP_Start_Character*. + By default, the mapped text starts at the first character on the line following the `#line` directive. However, this can be adjusted using *PP_Character_Offset*. If *PP_Character_Offset* is omitted, it defaults to 0; otherwise, it specifies the number of characters to skip in that next line. That number shall be non-negative and less than the length of the line following the `#line` directive. The mapping specified by a *PP_Line* is in scope until the following `#line` directive or the end of the compilation unit, whichever comes first.