Skip to content

Commit 8e5fa53

Browse files
authored
Better recover from missing closing } or ] in switch statements (#67862)
1 parent 78a7616 commit 8e5fa53

File tree

4 files changed

+680
-74
lines changed

4 files changed

+680
-74
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ internal enum TerminatorState
8585
IsEndOfFunctionPointerParameterListErrored = 1 << 24,
8686
IsEndOfFunctionPointerCallingConvention = 1 << 25,
8787
IsEndOfRecordOrClassOrStructOrInterfaceSignature = 1 << 26,
88+
IsExpressionOrPatternInCaseLabelOfSwitchStatement = 1 << 27,
8889
}
8990

90-
private const int LastTerminatorState = (int)TerminatorState.IsEndOfRecordOrClassOrStructOrInterfaceSignature;
91+
private const int LastTerminatorState = (int)TerminatorState.IsExpressionOrPatternInCaseLabelOfSwitchStatement;
9192

9293
private bool IsTerminator()
9394
{

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
65
using System.Diagnostics;
76
using System.Diagnostics.CodeAnalysis;
87
using Roslyn.Utilities;
@@ -50,6 +49,7 @@ when ConvertExpressionToType(expr, out var leftType):
5049
return false;
5150
};
5251
}
52+
5353
private PatternSyntax ParsePattern(Precedence precedence, bool afterIs = false, bool whenIsKeyword = false)
5454
{
5555
return ParseDisjunctivePattern(precedence, afterIs, whenIsKeyword);
@@ -429,7 +429,10 @@ private bool IsValidPatternDesignation(bool whenIsKeyword)
429429

430430
private CSharpSyntaxNode ParseExpressionOrPatternForSwitchStatement()
431431
{
432+
var savedState = _termState;
433+
_termState |= TerminatorState.IsExpressionOrPatternInCaseLabelOfSwitchStatement;
432434
var pattern = ParsePattern(Precedence.Conditional, whenIsKeyword: true);
435+
_termState = savedState;
433436
return ConvertPatternToExpressionIfPossible(pattern);
434437
}
435438

@@ -536,6 +539,13 @@ private static PostSkipAction SkipBadPatternListTokens<T>(
536539
if (@this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBraceToken or SyntaxKind.CloseBracketToken or SyntaxKind.SemicolonToken)
537540
return PostSkipAction.Abort;
538541

542+
// `:` is usually treated as incorrect separation token. This helps for error recovery in basic typing scenarios like `{ Prop:$$ Prop1: { ... } }`.
543+
// However, such behavior isn't much desirable when parsing pattern of a case label in a switch statement. For instance, consider the following example: `case { Prop: { }: case ...`.
544+
// Normally we would skip second `:` and `case` keyword after it as bad tokens and continue parsing pattern, which produces a lot of noise errors.
545+
// In order to avoid that and produce single error of missing `}` we exit on unexpected `:` in such cases.
546+
if (@this._termState.HasFlag(TerminatorState.IsExpressionOrPatternInCaseLabelOfSwitchStatement) && @this.CurrentToken.Kind is SyntaxKind.ColonToken)
547+
return PostSkipAction.Abort;
548+
539549
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list,
540550
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleSubpatternElement(),
541551
static (p, closeKind) => p.CurrentToken.Kind == closeKind || p.CurrentToken.Kind == SyntaxKind.SemicolonToken,

src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs

Lines changed: 33 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,32 +2155,20 @@ public void BrokenPattern_06()
21552155
// (1,30): error CS0103: The name 'e' does not exist in the current context
21562156
// class C { void M() { switch (e) { case (: ; } } }
21572157
Diagnostic(ErrorCode.ERR_NameNotInContext, "e").WithArguments("e").WithLocation(1, 30),
2158-
// (1,35): error CS8070: Control cannot fall out of switch from final case label ('case (: ')
2158+
// (1,35): error CS8070: Control cannot fall out of switch from final case label ('case (:')
21592159
// class C { void M() { switch (e) { case (: ; } } }
2160-
Diagnostic(ErrorCode.ERR_SwitchFallOut, "case (: ").WithArguments("case (: ").WithLocation(1, 35),
2160+
Diagnostic(ErrorCode.ERR_SwitchFallOut, "case (:").WithArguments("case (:").WithLocation(1, 35),
21612161
// (1,40): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
21622162
// class C { void M() { switch (e) { case (: ; } } }
2163-
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "(: ").WithArguments("recursive patterns", "8.0").WithLocation(1, 40),
2164-
// (1,41): error CS1001: Identifier expected
2163+
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "(").WithArguments("recursive patterns", "8.0").WithLocation(1, 40),
2164+
// (1,41): error CS1026: ) expected
21652165
// class C { void M() { switch (e) { case (: ; } } }
2166-
Diagnostic(ErrorCode.ERR_IdentifierExpected, ":").WithLocation(1, 41),
2167-
// (1,43): error CS1026: ) expected
2168-
// class C { void M() { switch (e) { case (: ; } } }
2169-
Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(1, 43),
2170-
// (1,43): error CS1003: Syntax error, ':' expected
2171-
// class C { void M() { switch (e) { case (: ; } } }
2172-
Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(":").WithLocation(1, 43));
2166+
Diagnostic(ErrorCode.ERR_CloseParenExpected, ":").WithLocation(1, 41));
21732167

21742168
UsingStatement(test, TestOptions.RegularWithoutRecursivePatterns,
2175-
// (1,20): error CS1001: Identifier expected
2176-
// switch (e) { case (: ; }
2177-
Diagnostic(ErrorCode.ERR_IdentifierExpected, ":").WithLocation(1, 20),
2178-
// (1,22): error CS1026: ) expected
2179-
// switch (e) { case (: ; }
2180-
Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(1, 22),
2181-
// (1,22): error CS1003: Syntax error, ':' expected
2169+
// (1,20): error CS1026: ) expected
21822170
// switch (e) { case (: ; }
2183-
Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(":").WithLocation(1, 22));
2171+
Diagnostic(ErrorCode.ERR_CloseParenExpected, ":").WithLocation(1, 20));
21842172
N(SyntaxKind.SwitchStatement);
21852173
{
21862174
N(SyntaxKind.SwitchKeyword);
@@ -2204,7 +2192,7 @@ public void BrokenPattern_06()
22042192
M(SyntaxKind.CloseParenToken);
22052193
}
22062194
}
2207-
M(SyntaxKind.ColonToken);
2195+
N(SyntaxKind.ColonToken);
22082196
}
22092197
N(SyntaxKind.EmptyStatement);
22102198
{
@@ -2737,47 +2725,26 @@ public void BrokenRecursivePattern01()
27372725
Diagnostic(ErrorCode.ERR_NameNotInContext, "e").WithArguments("e").WithLocation(1, 30),
27382726
// (1,40): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
27392727
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
2740-
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "T( : Q x ").WithArguments("recursive patterns", "8.0").WithLocation(1, 40),
2728+
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "T( ").WithArguments("recursive patterns", "8.0").WithLocation(1, 40),
27412729
// (1,40): error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)
27422730
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
27432731
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "T").WithArguments("T").WithLocation(1, 40),
2744-
// (1,43): error CS1001: Identifier expected
2745-
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
2746-
Diagnostic(ErrorCode.ERR_IdentifierExpected, ":").WithLocation(1, 43),
27472732
// (1,45): error CS0246: The type or namespace name 'Q' could not be found (are you missing a using directive or an assembly reference?)
27482733
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
27492734
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Q").WithArguments("Q").WithLocation(1, 45),
2750-
// (1,49): error CS1026: ) expected
2751-
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
2752-
Diagnostic(ErrorCode.ERR_CloseParenExpected, "=").WithLocation(1, 49),
2753-
// (1,49): error CS1003: Syntax error, ':' expected
2754-
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
2755-
Diagnostic(ErrorCode.ERR_SyntaxError, "=").WithArguments(":").WithLocation(1, 49),
2756-
// (1,49): error CS1525: Invalid expression term '='
2735+
// (1,43): error CS1026: ) expected
27572736
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
2758-
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "=").WithArguments("=").WithLocation(1, 49),
2737+
Diagnostic(ErrorCode.ERR_CloseParenExpected, ":").WithLocation(1, 43),
27592738
// (1,51): error CS0103: The name 'n' does not exist in the current context
27602739
// class C { void M() { switch (e) { case T( : Q x = n; break; } } }
27612740
Diagnostic(ErrorCode.ERR_NameNotInContext, "n").WithArguments("n").WithLocation(1, 51));
27622741

27632742
// This put the parser into an infinite loop at one time. The precise diagnostics and nodes
27642743
// are not as important as the fact that it terminates.
27652744
UsingStatement(test, TestOptions.RegularWithoutRecursivePatterns,
2766-
// (1,22): error CS1001: Identifier expected
2767-
// switch (e) { case T( : Q x = n; break; }
2768-
Diagnostic(ErrorCode.ERR_IdentifierExpected, ":").WithLocation(1, 22),
2769-
// (1,28): error CS1003: Syntax error, ',' expected
2770-
// switch (e) { case T( : Q x = n; break; }
2771-
Diagnostic(ErrorCode.ERR_SyntaxError, "=").WithArguments(",").WithLocation(1, 28),
2772-
// (1,30): error CS1003: Syntax error, ',' expected
2773-
// switch (e) { case T( : Q x = n; break; }
2774-
Diagnostic(ErrorCode.ERR_SyntaxError, "n").WithArguments(",").WithLocation(1, 30),
2775-
// (1,31): error CS1026: ) expected
2776-
// switch (e) { case T( : Q x = n; break; }
2777-
Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(1, 31),
2778-
// (1,31): error CS1003: Syntax error, ':' expected
2745+
// (1,22): error CS1026: ) expected
27792746
// switch (e) { case T( : Q x = n; break; }
2780-
Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(":").WithLocation(1, 31)
2747+
Diagnostic(ErrorCode.ERR_CloseParenExpected, ":").WithLocation(1, 22)
27812748
);
27822749
N(SyntaxKind.SwitchStatement);
27832750
{
@@ -2803,38 +2770,32 @@ public void BrokenRecursivePattern01()
28032770
N(SyntaxKind.PositionalPatternClause);
28042771
{
28052772
N(SyntaxKind.OpenParenToken);
2806-
N(SyntaxKind.Subpattern);
2807-
{
2808-
N(SyntaxKind.DeclarationPattern);
2809-
{
2810-
N(SyntaxKind.IdentifierName);
2811-
{
2812-
N(SyntaxKind.IdentifierToken, "Q");
2813-
}
2814-
N(SyntaxKind.SingleVariableDesignation);
2815-
{
2816-
N(SyntaxKind.IdentifierToken, "x");
2817-
}
2818-
}
2819-
}
2820-
M(SyntaxKind.CommaToken);
2821-
N(SyntaxKind.Subpattern);
2773+
M(SyntaxKind.CloseParenToken);
2774+
}
2775+
}
2776+
N(SyntaxKind.ColonToken);
2777+
}
2778+
N(SyntaxKind.LocalDeclarationStatement);
2779+
{
2780+
N(SyntaxKind.VariableDeclaration);
2781+
{
2782+
N(SyntaxKind.IdentifierName);
2783+
{
2784+
N(SyntaxKind.IdentifierToken, "Q");
2785+
}
2786+
N(SyntaxKind.VariableDeclarator);
2787+
{
2788+
N(SyntaxKind.IdentifierToken, "x");
2789+
N(SyntaxKind.EqualsValueClause);
28222790
{
2823-
N(SyntaxKind.ConstantPattern);
2791+
N(SyntaxKind.EqualsToken);
2792+
N(SyntaxKind.IdentifierName);
28242793
{
2825-
N(SyntaxKind.IdentifierName);
2826-
{
2827-
N(SyntaxKind.IdentifierToken, "n");
2828-
}
2794+
N(SyntaxKind.IdentifierToken, "n");
28292795
}
28302796
}
2831-
M(SyntaxKind.CloseParenToken);
28322797
}
28332798
}
2834-
M(SyntaxKind.ColonToken);
2835-
}
2836-
N(SyntaxKind.EmptyStatement);
2837-
{
28382799
N(SyntaxKind.SemicolonToken);
28392800
}
28402801
N(SyntaxKind.BreakStatement);

0 commit comments

Comments
 (0)