diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs index f56f8fd08d0c1..637ad1fa0de17 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs @@ -137,11 +137,11 @@ private bool LooksLikeTypeOfPattern(SyntaxKind tk) // // Parse an expression where a declaration expression would be permitted. This is suitable for use after // the `out` keyword in an argument list, or in the elements of a tuple literal (because they may - // be on the left-hand-side of a deconstruction). The first element of a tuple is handled slightly + // be on the left-hand-side of a positional). The first element of a tuple is handled slightly // differently, as we check for the comma before concluding that the identifier should cause a // disambiguation. For example, for the input `(A < B , C > D)`, we treat this as a tuple with // two elements, because if we considered the `A` to be a type, it wouldn't be a tuple at - // all. Since we don't have such a thing as a one-element tuple (even for deconstruction), the + // all. Since we don't have such a thing as a one-element tuple (even for positional), the // absence of the comma after the `D` means we don't treat the `D` as contributing to the // disambiguation of the expression/type. More formally, ... // @@ -283,7 +283,7 @@ private bool ScanDesignation(bool permitTuple) /// pattern /// : declaration_pattern /// | constant_pattern - /// | deconstruction_pattern + /// | positional_pattern /// | property_pattern /// | discard_pattern /// ; @@ -293,7 +293,7 @@ private bool ScanDesignation(bool permitTuple) /// constant_pattern /// : expression /// ; - /// deconstruction_pattern + /// positional_pattern /// : type? '(' subpatterns? ')' property_subpattern? identifier? /// ; /// subpatterns @@ -446,7 +446,7 @@ private PatternSyntax ParsePatternContinued(TypeSyntax type, bool whenIsKeyword) { if (subPatterns[0].Pattern is ConstantPatternSyntax cp) { - // There is an ambiguity between a deconstruction pattern `(` pattern `)` + // There is an ambiguity between a positional pattern `(` pattern `)` // and a constant expression pattern that happens to be parenthesized. // Per 2017-11-20 LDM we treat such syntax as a parenthesized expression always. return _syntaxFactory.ConstantPattern(_syntaxFactory.ParenthesizedExpression(openParenToken, cp.Expression, closeParenToken)); @@ -577,6 +577,10 @@ private void ParseSubpatternList( else if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleSubpatternElement()) { list.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + if (this.CurrentToken.Kind == SyntaxKind.CloseBraceToken) + { + break; + } list.Add(this.ParseSubpatternElement()); continue; } @@ -668,20 +672,11 @@ private SeparatedSyntaxList ParseSwitchExpressionArms var expression = ParseExpressionCore(); var switchExpressionCase = _syntaxFactory.SwitchExpressionArm(pattern, whenClause, arrow, expression); arms.Add(switchExpressionCase); - if (this.CurrentToken.Kind == SyntaxKind.CommaToken) + if (this.CurrentToken.Kind != SyntaxKind.CloseBraceToken) { - var commaToken = this.EatToken(); - if (this.CurrentToken.Kind == SyntaxKind.CloseBraceToken) - { - commaToken = this.AddError(commaToken, ErrorCode.ERR_UnexpectedToken, this.CurrentToken.Text); - } - + var commaToken = this.EatToken(SyntaxKind.CommaToken); arms.AddSeparator(commaToken); } - else - { - break; - } } SeparatedSyntaxList result = arms; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs index c9a80f8ddcee9..336d81f707dfb 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs @@ -375,13 +375,13 @@ public void SwitchExpression_01() { public static void Main() { - var r = 1 switch { _ => 0 }; + var r = 1 switch { _ => 0, }; } }"; CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularWithoutRecursivePatterns).VerifyDiagnostics( // (5,17): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater. - // var r = 1 switch { _ => 0 }; - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "1 switch { _ => 0 }").WithArguments("recursive patterns", "8.0").WithLocation(5, 17) + // var r = 1 switch { _ => 0, }; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "1 switch { _ => 0, }").WithArguments("recursive patterns", "8.0").WithLocation(5, 17) ); } @@ -433,48 +433,15 @@ public static void Main() // (6,34): error CS1525: Invalid expression term '?' // var r1 = b switch { true ? true : true => true, false => false }; Diagnostic(ErrorCode.ERR_InvalidExprTerm, "?").WithArguments("?").WithLocation(6, 34), - // (6,48): error CS1513: } expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_RbraceExpected, "=>").WithLocation(6, 48), // (6,48): error CS1003: Syntax error, ',' expected // var r1 = b switch { true ? true : true => true, false => false }; Diagnostic(ErrorCode.ERR_SyntaxError, "=>").WithArguments(",", "=>").WithLocation(6, 48), - // (6,51): error CS1002: ; expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_SemicolonExpected, "true").WithLocation(6, 51), - // (6,55): error CS1002: ; expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_SemicolonExpected, ",").WithLocation(6, 55), - // (6,55): error CS1513: } expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_RbraceExpected, ",").WithLocation(6, 55), - // (6,63): error CS1002: ; expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_SemicolonExpected, "=>").WithLocation(6, 63), - // (6,63): error CS1513: } expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_RbraceExpected, "=>").WithLocation(6, 63), - // (6,72): error CS1002: ; expected - // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(6, 72), - // (6,73): error CS1597: Semicolon after method or accessor block is not valid + // (6,48): error CS8504: Pattern missing // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.ERR_UnexpectedSemicolon, ";").WithLocation(6, 73), - // (9,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(9, 1), - // (7,9): error CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code - // var r2 = b switch { (true ? true : true) => true, false => false }; - Diagnostic(ErrorCode.ERR_TypeVarNotFound, "var").WithLocation(7, 9), - // (7,18): error CS0103: The name 'b' does not exist in the current context - // var r2 = b switch { (true ? true : true) => true, false => false }; - Diagnostic(ErrorCode.ERR_NameNotInContext, "b").WithArguments("b").WithLocation(7, 18), - // (7,20): warning CS8409: The switch expression does not handle all possible inputs (it is not exhaustive). - // var r2 = b switch { (true ? true : true) => true, false => false }; - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 20), - // (6,20): warning CS8409: The switch expression does not handle all possible inputs (it is not exhaustive). + Diagnostic(ErrorCode.ERR_MissingPattern, "=>").WithLocation(6, 48), + // (6,57): error CS8510: The pattern has already been handled by a previous arm of the switch expression. // var r1 = b switch { true ? true : true => true, false => false }; - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 20) + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "false").WithLocation(6, 57) ); } @@ -488,7 +455,7 @@ public void SwitchExpression_04() public static void Main() { var b = (true, false); - var r1 = b switch { (true ? true : true, _) => true, _ => false }; + var r1 = b switch { (true ? true : true, _) => true, _ => false, }; var r2 = b is (true ? true : true, _); switch (b.Item1) { case true ? true : true: break; } } @@ -609,7 +576,7 @@ public static void Main() { int q = 1; int u; - var x = q switch { 0 => u=0, 1 => u=M(u), _ => u=2 }; + var x = q switch { 0 => u=0, 1 => u=M(u), _ => u=2, }; System.Console.WriteLine(u); } static int M(int i) => i; @@ -633,9 +600,9 @@ public void SwitchExpression_10() public static void Main() { int a = 1; - var b = a switch { var x1 => x1 }; + var b = a switch { var x1 => x1, }; var c = a switch { var x2 when x2 is var x3 => x3 }; - var d = a switch { var x4 => x4 is var x5 ? x5 : 1 }; + var d = a switch { var x4 => x4 is var x5 ? x5 : 1, }; } static int M(int i) => i; }"; @@ -2066,13 +2033,13 @@ class Point }; var compilation = CreateCompilation(source, options: TestOptions.ReleaseDll); compilation.VerifyDiagnostics( - // (10,24): error CS8400: Pattern missing + // (10,24): error CS8504: Pattern missing // if (p is Point(, { })) { } Diagnostic(ErrorCode.ERR_MissingPattern, ",").WithLocation(10, 24), // (9,23): error CS1501: No overload for method 'Deconstruct' takes 3 arguments // if (p is Point({ }, { }, { })) { } Diagnostic(ErrorCode.ERR_BadArgCount, "({ }, { }, { })").WithArguments("Deconstruct", "3").WithLocation(9, 23), - // (9,23): error CS8129: No suitable Deconstruct instance or extension method was found for type 'Point', with 3 out parameters and a void return type. + // (9,23): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'Point', with 3 out parameters and a void return type. // if (p is Point({ }, { }, { })) { } Diagnostic(ErrorCode.ERR_MissingDeconstruct, "({ }, { }, { })").WithArguments("Point", "3").WithLocation(9, 23) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs index 02cc9e05764dc..61a6cb65e3024 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs @@ -79,7 +79,7 @@ class Program public static void Main() { Point p = null; - Console.WriteLine(p is { X: 3, Y: 4 }); + Console.WriteLine(p is { X: 3, Y: 4, }); } } interface I1 @@ -144,7 +144,7 @@ class Program public static void Main() { Point p = null; - Console.WriteLine(p is { X: 3, Y: 4 }); + Console.WriteLine(p is { X: 3, Y: 4, }); } } class Point @@ -411,7 +411,7 @@ public static void Main() { switch (anon) { - case { B: true, V: var val }: + case { B: true, V: var val, }: sum += val; break; } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs index ffa7b1ad969f3..649309b54b712 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs @@ -1641,7 +1641,7 @@ static int M1(bool? b1, bool? b2) (false, false) => 1, (false, true) => 2, // (true, false) => 3, - (true, true) => 4 + (true, true) => 4, }; } } @@ -1667,7 +1667,7 @@ static int M1(bool? b1, bool? b2) (false, false) => 1, (false, true) => 2, (true, false) => 3, - (true, true) => 4 + (true, true) => 4, }; } } @@ -1718,7 +1718,7 @@ static int M1(bool? b1, bool? b2) (true, false) => 3, (true, true) => 4, _ => 5, - (null, true) => 6 + (null, true) => 6, }; } } @@ -1726,7 +1726,7 @@ static int M1(bool? b1, bool? b2) var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( // (13,13): error CS8510: The pattern has already been handled by a previous arm of the switch expression. - // (null, true) => 6 + // (null, true) => 6, Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "(null, true)").WithLocation(13, 13) ); } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs index 83dddd19c93d3..39c3c062b21a6 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -//#define PARSING_TESTS_DUMP +#define PARSING_TESTS_DUMP using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs index c759bcd71685f..268e2bf7bf289 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs @@ -5726,7 +5726,6 @@ public void ShortTuplePatterns() EOF(); } - [Fact] public void NestedShortTuplePatterns() { @@ -6253,5 +6252,268 @@ public void ParenthesizedSwitchCase() EOF(); } } + + [Fact] + public void TrailingCommaInSwitchExpression_01() + { + UsingExpression("1 switch { 1 => 2, }"); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "2"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } + + [Fact] + public void TrailingCommaInSwitchExpression_02() + { + UsingExpression("1 switch { , }", + // (1,12): error CS8504: Pattern missing + // 1 switch { , } + Diagnostic(ErrorCode.ERR_MissingPattern, ",").WithLocation(1, 12), + // (1,12): error CS1003: Syntax error, '=>' expected + // 1 switch { , } + Diagnostic(ErrorCode.ERR_SyntaxError, ",").WithArguments("=>", ",").WithLocation(1, 12), + // (1,12): error CS1525: Invalid expression term ',' + // 1 switch { , } + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ",").WithArguments(",").WithLocation(1, 12) + ); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + M(SyntaxKind.SwitchExpressionArm); + { + M(SyntaxKind.ConstantPattern); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.EqualsGreaterThanToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } + + [Fact] + public void TrailingCommaInPropertyPattern_01() + { + UsingExpression("e is { X: 3, }"); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "3"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + EOF(); + } + + [Fact] + public void TrailingCommaInPropertyPattern_02() + { + UsingExpression("e is { , }", + // (1,8): error CS8504: Pattern missing + // e is { , } + Diagnostic(ErrorCode.ERR_MissingPattern, ",").WithLocation(1, 8) + ); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + M(SyntaxKind.Subpattern); + { + M(SyntaxKind.ConstantPattern); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + EOF(); + } + + [Fact] + public void TrailingCommaInPositionalPattern_01() + { + UsingExpression("e is ( X: 3, )", + // (1,14): error CS8504: Pattern missing + // e is ( X: 3, ) + Diagnostic(ErrorCode.ERR_MissingPattern, ")").WithLocation(1, 14) + ); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PositionalPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "3"); + } + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.Subpattern); + { + M(SyntaxKind.ConstantPattern); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } + + [Fact] + public void TrailingCommaInPositionalPattern_02() + { + UsingExpression("e is ( , )", + // (1,8): error CS8504: Pattern missing + // e is ( , ) + Diagnostic(ErrorCode.ERR_MissingPattern, ",").WithLocation(1, 8), + // (1,10): error CS8504: Pattern missing + // e is ( , ) + Diagnostic(ErrorCode.ERR_MissingPattern, ")").WithLocation(1, 10) + ); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PositionalPatternClause); + { + N(SyntaxKind.OpenParenToken); + M(SyntaxKind.Subpattern); + { + M(SyntaxKind.ConstantPattern); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.Subpattern); + { + M(SyntaxKind.ConstantPattern); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } } }