Skip to content

First steps towards issue 1662 - #1825

Draft
jskeet wants to merge 1 commit into
dotnet:draft-v8from
jskeet:issue-1662
Draft

jskeet wants to merge 1 commit into
dotnet:draft-v8from
jskeet:issue-1662

Conversation

@jskeet

@jskeet jskeet commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

This is mostly to show how awful this could get. I'm hoping someone has a better suggestion for an approach.

This is mostly to show how awful this could get. I'm hoping someone
has a better suggestion for an approach.
@jskeet jskeet added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Oct 6, 2026
@jskeet
jskeet marked this pull request as draft October 6, 2026 07:04
@BillWagner

Copy link
Copy Markdown
Member

I asked GitHub Copilot to review this PR, issue #1662, and the relevant LDM notes. Here is the (unedited) response:

Review of dotnet/csharpstandard PR #1825

Scope and assessment

I reviewed the PR description, full diff, sole commit, comments, and checks, the complete discussion in issue #1662, and the relevant LDM notes and proposals in the local csharplang checkout. I also followed superseding decisions rather than treating proposal snapshots or the issue's AI-generated summary as authoritative.

Bottom line: Keep the PR in draft and substantially revise it. It is useful as an inventory of contexts, but it is not normative text: the central rule is a FIXME, most bullets have no consequence, several bullets are explicitly uncertain, and firm decisions are missing. The proposed “fullest expression” abstraction is not supported by the design record and does not naturally model switch labels, switch-expression arms, embedded-statement boundaries, or nested anonymous functions.

The successful grammar and example checks do not establish correctness because the change adds no completed executable examples or grammar. Markdownlint fails on the placeholder link at line 631. The renumber and Word-converter jobs completed their substantive processing but ultimately failed while attempting to create check runs (403 Resource not accessible by integration), so those two failures are primarily infrastructure evidence, not independent semantic validation.

Decision chronology

Date Source Status Implication for PR #1825
2016-04-06 meetings/2016/LDM-2016-04-06.md, “Pattern variables and multiple case labels” Firm Pattern variables in labels for one switch section share a declaration space; assignment depends on the entered label. A rule limited to variables “declared by an expression” cannot cover this case.
2016-04-12–22 meetings/2016/LDM-2016-04-12-22.md, “Out vars and their scope” Partly superseded Unified out-variable scope with pattern-variable scope, but its field-initializer and constructor-initializer conclusions were later changed. Use it for unification history, not final initializer rules.
2016-07-15 meetings/2016/LDM-2016-07-15.md, “Looser scope rules” Firm Option 3 makes blocks, for/foreach/using, and embedded statements boundaries. A variable in an if condition is in scope in both branches and after the if; a variable declared inside an unbraced embedded branch does not escape that branch.
2016-08-24 meetings/2016/LDM-2016-08-24.md, “Scope of expression variables in initializers” Mixed; initializer restriction superseded Constructor-initializer scope was intended to include the constructor body. The temporary prohibition on field initializers was superseded by C# 7.3.
2016-10-25–26 meetings/2016/LDM-2016-10-25-26.md, “Declaration expressions” Firm conceptual model Out variables were recast as declaration expressions. This supports common treatment, but does not itself formally adopt “expression variable” as standard terminology or define a “fullest expression.”
2016-11-30 meetings/2016/LDM-2016-11-30.md, “Scope of while condition expression variables” Firm Variables in while/for conditions and for iterators have narrow scope and a fresh lifetime per iteration. The for initializer is distinct. Scope and lifetime must be specified separately.
2016-12-07–14 meetings/2016/LDM-2016-12-07-14.md Firm for C# 7; partly superseded by C# 7.3 do conditions follow the narrow loop rule. Expression variables were prohibited in C# 7 query clauses to preserve design space; the prohibition was later lifted with clause-local scope.
2017-01-11 meetings/2017/LDM-2017-01-11.md Raw/tentative Discussion preferred broadening switch-case-header variable lifetime, not scope. This is useful history but is not clean final normative authority.
2018-01-03 meetings/2018/LDM-2018-01-03.md; proposals/csharp-7.3/expression-variables-in-initializers.md Firm Constructor initializer: initializer plus constructor body. Field/property initializer: only that initializing expression. Query: only the translated clause expression/lambda body. All are absent from the PR.
2019-02-23 proposal snapshot proposals/csharp-8.0/patterns.md, switch-expression grammar Firm syntax; incomplete scope account Switch-expression arms are independent declaration contexts in implemented behavior, but the proposal does not supply the missing general scope rule. The standard work must cover arms explicitly rather than infer them from “enclosing expression.”
2020-05-06 meetings/2020/LDM-2020-05-06.md, if (e is not int i) Firm; supersedes stale proposal text for this form The guard form was accepted for C# 9 and implemented. The older “no declarations beneath not” result in patterns3.md is not the final chronology for is not T x.

Findings

Normative correctness issues

Blocker — The operative rule is undefined and the abstraction is unsound

standard/basic-concepts.md:714–738 defines neither x nor any resulting scopes. It contains FIXME, ???, an indented design note, “Something about anonymous functions,” and an open question. These fragments cannot impose testable requirements.

The “fullest expression” idea has no supporting LDM decision. Scope is determined by the syntactic context containing the declaration, with explicit boundaries. In particular:

  • a switch-label pattern is not declared by an expression, yet the 2016-04-06 decision gives it scope and declaration-space behavior;
  • a switch-expression-arm pattern belongs to an arm-specific context, not to the “fullest” enclosing switch expression;
  • a declaration inside a lambda/local function must not escape into the invocation containing that anonymous function;
  • an embedded statement is a boundary even when it has no braces.

The final text should start from the established syntax-context rules, not try to derive them from expression size.

High — The definition excludes required pattern contexts

Lines 631 and 716 say the local is “declared by a pattern … or by an argument list” and then require an expression e. That does not naturally include a pattern in a switch_label or switch_expression_arm. Conversely, an argument list does not itself declare a variable; a declaration expression in an argument does.

This is not merely terminology: the 2016-04-06 switch-label rule and C# 8 arm behavior require distinct declaration spaces and scopes.

High — Loop rules do not distinguish scope from lifetime

Lines 723–726 list loop locations without rules. The 2016-11-30 decision separately establishes narrow scope and fresh per-iteration lifetime for while/for conditions and for iterators; 2016-12-07–14 extends this to do. The for_initializer is not equivalent: its declarations are available through the loop header and body under the existing for rule. A single expression-enclosure rule risks changing capture semantics as well as name visibility.

High — Function and embedded-statement boundaries are absent

Line 734 is only “Something about anonymous functions.” The C# 7 proposal explicitly covers expression-bodied lambdas and members, while the declaration-space rules require separate anonymous-function and local-function boundaries. The 2016-07-15 decision also requires every unbraced embedded statement to act as a boundary. Without these rules, nested declarations can appear to leak into an outer call, condition, or block.

Completeness gaps

High — Firm C# 7.3 contexts are omitted

The PR has no rules for constructor initializers, field initializers, property initializers, or query-clause expressions. The final 2018-01-03 decisions are unambiguous:

  • constructor initializer and constructor body;
  • only the field/property initializing expression;
  • only the query-clause expression represented by the translated lambda.

These are part of the shipped language and must not be left to expansion or inference.

High — The PR does not complete issue #1662

Issue #1662 also calls for synchronization with local-variable and definite-assignment clauses. This PR changes only the scope inventory in basic-concepts.md; it does not update the local-variable classification or verify that definite-assignment rules cover pattern variables and out variables consistently. A scoped first PR is reasonable, but its description and follow-up plan should say explicitly which normative obligations remain.

Medium — Switch statement and switch expression cases need explicit treatment

The PR lists only a switch statement's controlling expression at line 722. It omits:

  • pattern variables in switch labels and their shared switch-section declaration space;
  • definite assignment when a section has multiple reachable labels;
  • arm-local variables in switch expressions;
  • the distinction, raised in the 2017-01-11 raw notes, between switch-header variable scope and lifetime.

Editorial and link issues

  • Line 631: [§new](#expression-variables) is broken; Markdownlint reports MD051.
  • Lines 631 and 716: displayed §9.1.1 does not match the patterns.md#1111-general target.
  • Line 716: the general argument-list link does not identify out-variable declarations.
  • Line 722: “an switch” is a grammar error; displayed §13.8.2 conflicts with the switch target #1383.
  • Line 733: displayed §13.14 conflicts with the yield target #1315.
  • The placeholder heading 7.7.new and transition directly to 7.7.2 are draft scaffolding, not mergeable numbering.

Reliability of issue #1662's AI-generated summary

The issue discussion is useful research input, but its AI summary should not be cited as authority.

  1. It says condition variables do not “bleed into embedded sub-statements.” That is misleading. A variable declared in an if condition is in scope in both embedded branches and after the if; the boundary restricts variables declared inside an embedded branch.
  2. It overstates if as the only meaningful broad-scope case. Ordinary declaration/expression statements, method arguments, and switch controlling expressions can also place the variable in the containing scope.
  3. It calls the C# 9 prohibition beneath not final and says if (e is not int i) return; M(i); is unsupported. The later 2020-05-06 LDM accepted that form, and the implementation shipped. Jon Skeet flagged this error in the issue immediately after the AI summary.
  4. It treats proposal text as “authoritative” without consistently accounting for later LDM decisions. The 2016 initializer restrictions were superseded in 2018, and the C# 9 patterns3.md result is stale for the accepted is not T x guard form.
  5. Its terminology claim is too strong. LDM notes use “expression variables,” but issue Review whether there needs to be some syncing up between §11.2.2 and §9.2.9 *Local variables*, §9.4 *Definite assignment* #1662 correctly records formal adoption of that term in the standard as an open editorial/standards-group choice.

Recommended next steps

  1. Keep the PR draft; do not merge this commit.
  2. Replace the “fullest expression” premise with a complete, syntax-context-based scope model grounded in the final C# 7/C# 7.3 decisions.
  3. Cover switch labels and switch-expression arms explicitly; do not force them into “declared by an expression.”
  4. Add the firm constructor, field/property initializer, and query-clause rules.
  5. State loop scope independently from per-iteration lifetime, with separate treatment for initializer, condition, iterator, and body.
  6. Add anonymous-function, local-function, expression-bodied member, catch-filter, and embedded-statement boundaries.
  7. Decide and document whether this PR is scope-only. If so, open or link follow-ups for local-variable classification and definite assignment before closing Review whether there needs to be some syncing up between §11.2.2 and §9.2.9 *Local variables*, §9.4 *Definite assignment* #1662.
  8. Resolve the term “expression variable,” replace all placeholders, repair clause numbers/anchors, and rerun checks.

Unresolved standards-group questions

  • Should “expression variable” become a normative defined term, or remain an explanatory grouping for pattern and out variables?
  • Should scope be specified once through declaration spaces plus a short context table, or separately for pattern variables and declaration expressions?
  • Where should switch case-header lifetime be stated, given that scope and lifetime differ?
  • Is First steps towards issue 1662 #1825 intentionally limited to the C# 8 draft, and if so, how will later is not definite-assignment wording be tracked without importing stale C# 9 proposal text?
  • Should issue Review whether there needs to be some syncing up between §11.2.2 and §9.2.9 *Local variables*, §9.4 *Definite assignment* #1662 be split into independently reviewable scope, local-variable, and definite-assignment changes while retaining a single completion checklist?

- The scope of a local variable declared in a *for_initializer* of a `for` statement ([§13.9.4](statements.md#1394-the-for-statement)) is the *for_initializer*, *for_condition*, *for_iterator*, and *embedded_statement* of the `for` statement.
- The scope of a local constant declared in a *local_constant_declaration* ([§13.6.3](statements.md#1363-local-constant-declarations)) is the *block* in which the declaration occurs. It is a compile-time error to refer to a local constant in a textual position that precedes its *constant_declarator*.
- The scope of a variable declared as part of a *foreach_statement*, *using_statement*, *lock_statement* or *query_expression* is determined by the expansion of the given construct.
- The scope of a local variable declared by a pattern ([§9.1.1](patterns.md#1111-general)) or by an argument list ([§12.6.2.1](expressions.md#12621-general)) is described in [§new](#expression-variables).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to include variables declared by switch expressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

meeting: discuss This issue should be discussed at the next TC49-TG2 meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants