Skip to content

Commit fc2b820

Browse files
Fix parsing of parenthesized type in switch arm (#81863)
1 parent 76fc15b commit fc2b820

File tree

4 files changed

+1044
-7
lines changed

4 files changed

+1044
-7
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,22 @@ void M()
154154
```
155155

156156
See also https://github.com/dotnet/roslyn/issues/80954.
157+
158+
## Parsing of 'with' within a switch-expression-arm
159+
160+
***Introduced in Visual Studio 2026 version 18.4***
161+
162+
See https://github.com/dotnet/roslyn/issues/81837 and https://github.com/dotnet/roslyn/pull/81863
163+
164+
Previously, when seeing the following, the compiler would treat `(X.Y)when` as a cast-expression. Specifically,
165+
casting the contextual identifier `when` to `(X.Y)`:
166+
167+
```c#
168+
x switch
169+
{
170+
(X.Y) when
171+
}
172+
```
173+
174+
This was undesirable, and meant a simple `when` check of the pattern (like `(X.Y) when a > b =>`) would not
175+
parse properly. Now, this is treated as a constant pattern `(X.Y)` followed by a `when clause`.

src/Compilers/CSharp/Portable/Parser/LanguageParser.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12770,7 +12770,7 @@ private ExpressionSyntax ParseCastOrParenExpressionOrTuple()
1277012770
// expression? Because look-ahead is cheap with our token stream, we check
1277112771
// to see if this "looks like" a cast (without constructing any parse trees)
1277212772
// to help us make the decision.
12773-
if (this.ScanCast())
12773+
if (this.ScanCast(forPattern: false, inSwitchArmPattern: false))
1277412774
{
1277512775
if (!IsCurrentTokenQueryKeywordInQuery())
1277612776
{
@@ -12848,8 +12848,10 @@ private TupleExpressionSyntax ParseTupleExpressionTail(SyntaxToken openParen, Ar
1284812848
this.EatToken(SyntaxKind.CloseParenToken));
1284912849
}
1285012850

12851-
private bool ScanCast(bool forPattern = false)
12851+
private bool ScanCast(bool forPattern, bool inSwitchArmPattern)
1285212852
{
12853+
Debug.Assert(!inSwitchArmPattern || forPattern, "Can't be in a switch arm without also being in a pattern");
12854+
1285312855
if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
1285412856
{
1285512857
return false;
@@ -12872,8 +12874,20 @@ private bool ScanCast(bool forPattern = false)
1287212874

1287312875
if (forPattern && this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
1287412876
{
12875-
// In a pattern, an identifier can follow a cast unless it's a binary pattern token.
12876-
return !isBinaryPattern();
12877+
// In a pattern we might have a cast of a constant, or the start of a legal pattern form.
12878+
//
12879+
// For example: `(A.B) and ...` should be treated not as a 'cast' of some variable 'and', but instead as
12880+
// a conjunctive pattern.
12881+
if (isBinaryPattern())
12882+
return false;
12883+
12884+
// Similarly `(A.B) when` directly in a switch arm should be treated as the start of a `when` clause not
12885+
// a cast of a `when` variable when in a switch expression arm. This matches the exact checking logic
12886+
// in IsValidPatternDesignation.
12887+
if (inSwitchArmPattern && this.CurrentToken.ContextualKind == SyntaxKind.WhenKeyword)
12888+
return false;
12889+
12890+
return true;
1287712891
}
1287812892

1287912893
switch (type)

src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ bool parsePropertyPatternClause([NotNullWhen(true)] out PropertyPatternClauseSyn
393393
bool looksLikeCast()
394394
{
395395
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
396-
return this.ScanCast(forPattern: true);
396+
return this.ScanCast(forPattern: true, inSwitchArmPattern);
397397
}
398398
}
399399

@@ -404,14 +404,16 @@ bool looksLikeCast()
404404
: null;
405405
}
406406

407-
private bool IsValidPatternDesignation(bool whenIsKeyword)
407+
private bool IsValidPatternDesignation(bool inSwitchArmPattern)
408408
{
409409
if (CurrentToken.Kind == SyntaxKind.IdentifierToken)
410410
{
411411
switch (CurrentToken.ContextualKind)
412412
{
413413
case SyntaxKind.WhenKeyword:
414-
return !whenIsKeyword;
414+
// When directly in a switch arm, we *always* treat 'when' as a keyword starting the 'when
415+
// clause'. In other patterns, we allow 'when' to be a normal designator.
416+
return !inSwitchArmPattern;
415417
case SyntaxKind.AndKeyword:
416418
case SyntaxKind.OrKeyword:
417419
var tk = PeekToken(1).Kind;

0 commit comments

Comments
 (0)