From 379e7e922e8ad15f259697e2975d1de4a639ef71 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:07:48 -0700 Subject: [PATCH 01/13] Fix ambiguity parsing collection expressions vs conditional access expressions --- .../CSharp/Portable/Parser/LanguageParser.cs | 116 +++++-- .../Parsing/CollectionLiteralParsingTests.cs | 294 ++++++++++++++++++ 2 files changed, 384 insertions(+), 26 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 5cd8977afbc86..d06e91ab06939 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -3819,14 +3819,14 @@ private ArrowExpressionClauseSyntax ParseArrowExpressionClause() ParsePossibleRefExpression()); } - private ExpressionSyntax ParsePossibleRefExpression() + private ExpressionSyntax ParsePossibleRefExpression(bool forceConditionalAccessExpression = false) { // check for lambda expression with explicit ref return type: `ref int () => { ... }` var refKeyword = this.CurrentToken.Kind == SyntaxKind.RefKeyword && !this.IsPossibleLambdaExpression(Precedence.Expression) ? this.EatToken() : null; - var expression = this.ParseExpressionCore(); + var expression = this.ParseExpressionCore(forceConditionalAccessExpression); return refKeyword == null ? expression : _syntaxFactory.RefExpression(refKeyword, expression); } @@ -10005,9 +10005,9 @@ public ExpressionSyntax ParseExpression() static @this => @this.CreateMissingIdentifierName()); } - private ExpressionSyntax ParseExpressionCore() + private ExpressionSyntax ParseExpressionCore(bool forceConditionalAccessExpression = false) { - return this.ParseSubExpression(Precedence.Expression); + return this.ParseSubExpression(Precedence.Expression, forceConditionalAccessExpression); } /// @@ -10367,13 +10367,13 @@ private bool IsAwaitExpression() /// /// Parse a subexpression of the enclosing operator of the given precedence. /// - private ExpressionSyntax ParseSubExpression(Precedence precedence) + private ExpressionSyntax ParseSubExpression(Precedence precedence, bool forceConditionalAccessExpression = false) { _recursionDepth++; StackGuard.EnsureSufficientExecutionStack(_recursionDepth); - var result = ParseSubExpressionCore(precedence); + var result = ParseSubExpressionCore(precedence, forceConditionalAccessExpression); #if DEBUG // Ensure every expression kind is handled in GetPrecedence _ = GetPrecedence(result.Kind); @@ -10382,7 +10382,7 @@ private ExpressionSyntax ParseSubExpression(Precedence precedence) return result; } - private ExpressionSyntax ParseSubExpressionCore(Precedence precedence) + private ExpressionSyntax ParseSubExpressionCore(Precedence precedence, bool forceConditionalAccessExpression) { ExpressionSyntax leftOperand; Precedence newPrecedence = 0; @@ -10461,13 +10461,14 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence) else { // Not a unary operator - get a primary expression. - leftOperand = this.ParseTerm(precedence); + leftOperand = this.ParseTerm(precedence, forceConditionalAccessExpression); } - return ParseExpressionContinued(leftOperand, precedence); + return ParseExpressionContinued(leftOperand, precedence, forceConditionalAccessExpression); } - private ExpressionSyntax ParseExpressionContinued(ExpressionSyntax leftOperand, Precedence precedence) + private ExpressionSyntax ParseExpressionContinued( + ExpressionSyntax leftOperand, Precedence precedence, bool forceConditionalAccessExpression = false) { while (true) { @@ -10667,7 +10668,45 @@ private ExpressionSyntax ParseExpressionContinued(ExpressionSyntax leftOperand, if (CurrentToken.Kind == SyntaxKind.QuestionToken && precedence <= Precedence.Conditional) { var questionToken = this.EatToken(); - var colonLeft = this.ParsePossibleRefExpression(); + + var afterQuestionToken = this.GetResetPoint(); + var colonLeft = this.ParsePossibleRefExpression(forceConditionalAccessExpression); + + // We want a `:` here but don't have one. If the portion we parsed so far contains a `?[` sequence then + // it's possible that we interpreted a conditional-access expression as a ternary with a collection-literal + // which then consumed the colon we want ourselves. + // + // We want to retry parsing this again as a conditional-access expression to see if that helps us + // proceed. If so, then we'll prefer that original interpretation. + if (this.CurrentToken.Kind != SyntaxKind.ColonToken && + !forceConditionalAccessExpression && + containsTernaryCollectionToReinterpret(colonLeft)) + { + // Keep track of where we are right now in case the new parse doesn't make things better. + var originalEndPoint = this.GetResetPoint(); + + // Go back to right after the `?` + this.Reset(ref afterQuestionToken); + + // try reparsing with `?[` as a conditional access, not a ternary+collection + var newColonLeft = this.ParsePossibleRefExpression(forceConditionalAccessExpression: true); + + if (this.CurrentToken.Kind == SyntaxKind.ColonToken) + { + // if we now are at a colon, this was preferred parse. + colonLeft = newColonLeft; + } + else + { + // retrying teh parse didn't help. Use the original interpretation. + this.Reset(ref originalEndPoint); + } + + this.Release(ref originalEndPoint); + } + + this.Release(ref afterQuestionToken); + if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken && this.lexer.InterpolationFollowedByColon) { // We have an interpolated string with an interpolation that contains a conditional expression. @@ -10682,12 +10721,35 @@ private ExpressionSyntax ParseExpressionContinued(ExpressionSyntax leftOperand, else { var colon = this.EatToken(SyntaxKind.ColonToken); - var colonRight = this.ParsePossibleRefExpression(); + var colonRight = this.ParsePossibleRefExpression(forceConditionalAccessExpression); leftOperand = _syntaxFactory.ConditionalExpression(leftOperand, questionToken, colonLeft, colon, colonRight); } } return leftOperand; + + static bool containsTernaryCollectionToReinterpret(ExpressionSyntax expression) + { + var stack = ArrayBuilder.GetInstance(); + stack.Push(expression); + + while (stack.Count > 0) + { + var current = stack.Pop(); + if (current is ConditionalExpressionSyntax conditionalExpression && + conditionalExpression.WhenTrue.GetFirstToken().Kind == SyntaxKind.OpenBracketToken) + { + stack.Free(); + return true; + } + + foreach (var child in current.ChildNodesAndTokens()) + stack.Push(child); + } + + stack.Free(); + return false; + } } private DeclarationExpressionSyntax ParseDeclarationExpression(ParseTypeMode mode, bool isScoped) @@ -10720,8 +10782,8 @@ private ExpressionSyntax ParseIsExpression(ExpressionSyntax leftOperand, SyntaxT }; } - private ExpressionSyntax ParseTerm(Precedence precedence) - => this.ParsePostFixExpression(ParseTermWithoutPostfix(precedence)); + private ExpressionSyntax ParseTerm(Precedence precedence, bool forceConditionalAccessExpression) + => this.ParsePostFixExpression(ParseTermWithoutPostfix(precedence), forceConditionalAccessExpression); private ExpressionSyntax ParseTermWithoutPostfix(Precedence precedence) { @@ -10958,7 +11020,8 @@ private bool IsPossibleAnonymousMethodExpression() this.PeekToken(tokenIndex + 1).Kind != SyntaxKind.AsteriskToken; } - private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr) + private ExpressionSyntax ParsePostFixExpression( + ExpressionSyntax expr, bool forceConditionalAccessExpression) { Debug.Assert(expr != null); @@ -11024,12 +11087,12 @@ private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr) continue; case SyntaxKind.QuestionToken: - if (CanStartConsequenceExpression()) + if (CanStartConsequenceExpression(forceConditionalAccessExpression)) { expr = _syntaxFactory.ConditionalAccessExpression( expr, this.EatToken(), - ParseConsequenceSyntax()); + ParseConsequenceSyntax(forceConditionalAccessExpression)); continue; } @@ -11045,7 +11108,7 @@ private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr) } } - private bool CanStartConsequenceExpression() + private bool CanStartConsequenceExpression(bool forceConditionalAccessExpression) { Debug.Assert(this.CurrentToken.Kind == SyntaxKind.QuestionToken); var nextTokenKind = this.PeekToken(1).Kind; @@ -11056,14 +11119,15 @@ private bool CanStartConsequenceExpression() if (nextTokenKind == SyntaxKind.OpenBracketToken) { + if (forceConditionalAccessExpression) + return true; + // could simply be `x?[0]`, or could be `x ? [0] : [1]`. using var _ = GetDisposableResetPoint(resetOnDispose: true); this.EatToken(); - var collectionExpression = this.ParseCollectionCreationExpression(); + this.ParsePossibleRefExpression(); - // PROTOTYPE: Def back compat concern here. What if the user has `x ? y?[0] : z` this would be legal, - // but will now change to `y?[0] : z` being a ternary. Have to decide if this is acceptable break. return this.CurrentToken.Kind != SyntaxKind.ColonToken; } @@ -11071,7 +11135,7 @@ private bool CanStartConsequenceExpression() return false; } - internal ExpressionSyntax ParseConsequenceSyntax() + internal ExpressionSyntax ParseConsequenceSyntax(bool forceConditionalAccessExpression) { Debug.Assert(this.CurrentToken.Kind is SyntaxKind.DotToken or SyntaxKind.OpenBracketToken); ExpressionSyntax expr = this.CurrentToken.Kind switch @@ -11106,12 +11170,12 @@ internal ExpressionSyntax ParseConsequenceSyntax() continue; case SyntaxKind.QuestionToken: - return !CanStartConsequenceExpression() - ? expr - : _syntaxFactory.ConditionalAccessExpression( + return CanStartConsequenceExpression(forceConditionalAccessExpression) + ? _syntaxFactory.ConditionalAccessExpression( expr, operatorToken: this.EatToken(), - ParseConsequenceSyntax()); + ParseConsequenceSyntax(forceConditionalAccessExpression)) + : expr; default: return expr; diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs index 37a7cff132cd5..819959c78d40b 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs @@ -2421,6 +2421,300 @@ public void ConditionalAmbiguity2() EOF(); } + [Fact] + public void ConditionalAmbiguity3() + { + UsingExpression("a ? [b] : c"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionCreationExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity3A() + { + UsingExpression("a ? [b].M() : c"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.CollectionCreationExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity4() + { + UsingExpression("a ? b?[c] : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity4A() + { + UsingExpression("a ? b?[c].M() : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity5() + { + UsingExpression("a ? b ? [c] : d : e"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionCreationExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity5A() + { + UsingExpression("a ? b ? [c].M() : d : e"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.CollectionCreationExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + } + EOF(); + } + [Fact] public void CastVersusIndexAmbiguity1() { From a6f9465d9b61bb2ad26e3e4e030d124707e91655 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:27:34 -0700 Subject: [PATCH 02/13] Simplify --- .../CSharp/Portable/Parser/LanguageParser.cs | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index f223a2b8d5bf5..c0132b1f03a53 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -10665,66 +10665,69 @@ private ExpressionSyntax ParseExpressionContinued( // null-coalescing-expression ? expression : expression // // Only take the conditional if we're at or below its precedence. - if (CurrentToken.Kind == SyntaxKind.QuestionToken && precedence <= Precedence.Conditional) - { - var questionToken = this.EatToken(); - - var afterQuestionToken = this.GetResetPoint(); - var colonLeft = this.ParsePossibleRefExpression(forceConditionalAccessExpression); + if (CurrentToken.Kind != SyntaxKind.QuestionToken || precedence > Precedence.Conditional) + return leftOperand; - // We want a `:` here but don't have one. If the portion we parsed so far contains a `?[` sequence then - // it's possible that we interpreted a conditional-access expression as a ternary with a collection-literal - // which then consumed the colon we want ourselves. - // - // We want to retry parsing this again as a conditional-access expression to see if that helps us - // proceed. If so, then we'll prefer that original interpretation. - if (this.CurrentToken.Kind != SyntaxKind.ColonToken && - !forceConditionalAccessExpression && - containsTernaryCollectionToReinterpret(colonLeft)) - { - // Keep track of where we are right now in case the new parse doesn't make things better. - using var originalEndPoint = this.GetDisposableResetPoint(resetOnDispose: false); + // Complex ambiguity with `?` and collection-expressions. Specifically: b?[c]:d + // + // On its own, we want that to be a conditional expression with a collection expression in it. However, for + // back compat, we need to make sure that `a ? b?[c] : d` sees the inner `b?[c]` as a + // conditional-access-expression. So, if after consuming the portion after the initial `?` if we do not + // have the `:` we need, and we can see a `?[` in that portion of the parse, then we retry consuming the + // when-true portion, but this time forcing the prior way of handling `?[`. + var questionToken = this.EatToken(); - // Go back to right after the `?` - this.Reset(ref afterQuestionToken); + using var afterQuestionToken = this.GetDisposableResetPoint(resetOnDispose: false); + var whenTrue = this.ParsePossibleRefExpression(forceConditionalAccessExpression); - // try reparsing with `?[` as a conditional access, not a ternary+collection - var newColonLeft = this.ParsePossibleRefExpression(forceConditionalAccessExpression: true); + if (this.CurrentToken.Kind != SyntaxKind.ColonToken && + !forceConditionalAccessExpression && + containsTernaryCollectionToReinterpret(whenTrue)) + { + // Keep track of where we are right now in case the new parse doesn't make things better. + using var originalAfterWhenTrue = this.GetDisposableResetPoint(resetOnDispose: false); - if (this.CurrentToken.Kind == SyntaxKind.ColonToken) - { - // if we now are at a colon, this was preferred parse. - colonLeft = newColonLeft; - } - else - { - // retrying teh parse didn't help. Use the original interpretation. - originalEndPoint.Reset(); - } - } + // Go back to right after the `?` + afterQuestionToken.Reset(); - this.Release(ref afterQuestionToken); + // try reparsing with `?[` as a conditional access, not a ternary+collection + var newWhenTrue = this.ParsePossibleRefExpression(forceConditionalAccessExpression: true); - if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken && this.lexer.InterpolationFollowedByColon) + if (this.CurrentToken.Kind == SyntaxKind.ColonToken) { - // We have an interpolated string with an interpolation that contains a conditional expression. - // Unfortunately, the precedence demands that the colon is considered to signal the start of the - // format string. Without this code, the compiler would complain about a missing colon, and point - // to the colon that is present, which would be confusing. We aim to give a better error message. - var colon = SyntaxFactory.MissingToken(SyntaxKind.ColonToken); - var colonRight = _syntaxFactory.IdentifierName(SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken)); - leftOperand = _syntaxFactory.ConditionalExpression(leftOperand, questionToken, colonLeft, colon, colonRight); - leftOperand = this.AddError(leftOperand, ErrorCode.ERR_ConditionalInInterpolation); + // if we now are at a colon, this was preferred parse. + whenTrue = newWhenTrue; } else { - var colon = this.EatToken(SyntaxKind.ColonToken); - var colonRight = this.ParsePossibleRefExpression(forceConditionalAccessExpression); - leftOperand = _syntaxFactory.ConditionalExpression(leftOperand, questionToken, colonLeft, colon, colonRight); + // retrying the parse didn't help. Use the original interpretation. + originalAfterWhenTrue.Reset(); } } - return leftOperand; + if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken && this.lexer.InterpolationFollowedByColon) + { + // We have an interpolated string with an interpolation that contains a conditional expression. + // Unfortunately, the precedence demands that the colon is considered to signal the start of the + // format string. Without this code, the compiler would complain about a missing colon, and point + // to the colon that is present, which would be confusing. We aim to give a better error message. + leftOperand = _syntaxFactory.ConditionalExpression( + leftOperand, + questionToken, + whenTrue, + SyntaxFactory.MissingToken(SyntaxKind.ColonToken), + _syntaxFactory.IdentifierName(SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken))); + return this.AddError(leftOperand, ErrorCode.ERR_ConditionalInInterpolation); + } + else + { + return _syntaxFactory.ConditionalExpression( + leftOperand, + questionToken, + whenTrue, + this.EatToken(SyntaxKind.ColonToken), + this.ParsePossibleRefExpression(forceConditionalAccessExpression)); + } static bool containsTernaryCollectionToReinterpret(ExpressionSyntax expression) { From 714d42308541450d10a3d552f34e46307653c744 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:32:07 -0700 Subject: [PATCH 03/13] Fix test --- .../Parsing/LambdaAttributeParsingTests.cs | 72 ++++++++++++++----- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs index 3d02488363451..f5905982764a6 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaAttributeParsingTests.cs @@ -1467,40 +1467,80 @@ public void As() [Fact] public void ConditionalExpression_01() { - UsingExpression("x ? [A] () => { } : z", TestOptions.RegularPreview, - // (1,1): error CS1073: Unexpected token '=>' - // x ? [A] () => { } : z - Diagnostic(ErrorCode.ERR_UnexpectedToken, "x ? [A] ()").WithArguments("=>").WithLocation(1, 1)); + UsingExpression("x ? [A] () => { } : z", TestOptions.RegularPreview); - N(SyntaxKind.ConditionalAccessExpression); + N(SyntaxKind.ConditionalExpression); { N(SyntaxKind.IdentifierName); { N(SyntaxKind.IdentifierToken, "x"); } N(SyntaxKind.QuestionToken); - N(SyntaxKind.InvocationExpression); + N(SyntaxKind.ParenthesizedLambdaExpression); { - N(SyntaxKind.ElementBindingExpression); + N(SyntaxKind.AttributeList); { - N(SyntaxKind.BracketedArgumentList); + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Attribute); { - N(SyntaxKind.OpenBracketToken); - N(SyntaxKind.Argument); + N(SyntaxKind.IdentifierName); { - N(SyntaxKind.IdentifierName); - { - N(SyntaxKind.IdentifierToken, "A"); - } + N(SyntaxKind.IdentifierToken, "A"); } - N(SyntaxKind.CloseBracketToken); } + N(SyntaxKind.CloseBracketToken); } - N(SyntaxKind.ArgumentList); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); + } + } + EOF(); + } + + [Fact] + public void ConditionalExpression_01_A() + { + UsingExpression("x ? () => { } : z", TestOptions.RegularPreview); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); { N(SyntaxKind.OpenParenToken); N(SyntaxKind.CloseParenToken); } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); } } EOF(); From c4fc6e9772f7fd6d462fb40173bb9c8d20578eb9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:42:51 -0700 Subject: [PATCH 04/13] Add tests --- .../Parsing/CollectionLiteralParsingTests.cs | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs index e2debd6304e31..96f98f47e33b6 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs @@ -2715,6 +2715,218 @@ public void ConditionalAmbiguity5A() EOF(); } + [Fact] + public void ConditionalAmbiguity6() + { + UsingExpression("a?[c] ? b : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity6A() + { + UsingExpression("a?[c].M() ? b : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity7() + { + UsingExpression("a?[c] ? b : d : e"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity7A() + { + UsingExpression("a?[c].M() ? b : d : e"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "M"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + } + EOF(); + } + [Fact] public void CastVersusIndexAmbiguity1() { From 50b04c7914a1f58f7715ce1e182bbebf51a79ce9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:56:14 -0700 Subject: [PATCH 05/13] Switch to a simpler contextual model --- .../CSharp/Portable/Parser/LanguageParser.cs | 62 +++++++++++-------- .../Portable/Parser/SyntaxFactoryContext.cs | 6 ++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index c0132b1f03a53..4c8b42d4b9350 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -3819,14 +3819,14 @@ private ArrowExpressionClauseSyntax ParseArrowExpressionClause() ParsePossibleRefExpression()); } - private ExpressionSyntax ParsePossibleRefExpression(bool forceConditionalAccessExpression = false) + private ExpressionSyntax ParsePossibleRefExpression() { // check for lambda expression with explicit ref return type: `ref int () => { ... }` var refKeyword = this.CurrentToken.Kind == SyntaxKind.RefKeyword && !this.IsPossibleLambdaExpression(Precedence.Expression) ? this.EatToken() : null; - var expression = this.ParseExpressionCore(forceConditionalAccessExpression); + var expression = this.ParseExpressionCore(); return refKeyword == null ? expression : _syntaxFactory.RefExpression(refKeyword, expression); } @@ -10005,9 +10005,9 @@ public ExpressionSyntax ParseExpression() static @this => @this.CreateMissingIdentifierName()); } - private ExpressionSyntax ParseExpressionCore(bool forceConditionalAccessExpression = false) + private ExpressionSyntax ParseExpressionCore() { - return this.ParseSubExpression(Precedence.Expression, forceConditionalAccessExpression); + return this.ParseSubExpression(Precedence.Expression); } /// @@ -10367,13 +10367,13 @@ private bool IsAwaitExpression() /// /// Parse a subexpression of the enclosing operator of the given precedence. /// - private ExpressionSyntax ParseSubExpression(Precedence precedence, bool forceConditionalAccessExpression = false) + private ExpressionSyntax ParseSubExpression(Precedence precedence) { _recursionDepth++; StackGuard.EnsureSufficientExecutionStack(_recursionDepth); - var result = ParseSubExpressionCore(precedence, forceConditionalAccessExpression); + var result = ParseSubExpressionCore(precedence); #if DEBUG // Ensure every expression kind is handled in GetPrecedence _ = GetPrecedence(result.Kind); @@ -10382,7 +10382,7 @@ private ExpressionSyntax ParseSubExpression(Precedence precedence, bool forceCon return result; } - private ExpressionSyntax ParseSubExpressionCore(Precedence precedence, bool forceConditionalAccessExpression) + private ExpressionSyntax ParseSubExpressionCore(Precedence precedence) { ExpressionSyntax leftOperand; Precedence newPrecedence = 0; @@ -10461,14 +10461,14 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence, bool forc else { // Not a unary operator - get a primary expression. - leftOperand = this.ParseTerm(precedence, forceConditionalAccessExpression); + leftOperand = this.ParseTerm(precedence); } - return ParseExpressionContinued(leftOperand, precedence, forceConditionalAccessExpression); + return ParseExpressionContinued(leftOperand, precedence); } private ExpressionSyntax ParseExpressionContinued( - ExpressionSyntax leftOperand, Precedence precedence, bool forceConditionalAccessExpression = false) + ExpressionSyntax leftOperand, Precedence precedence) { while (true) { @@ -10678,10 +10678,10 @@ private ExpressionSyntax ParseExpressionContinued( var questionToken = this.EatToken(); using var afterQuestionToken = this.GetDisposableResetPoint(resetOnDispose: false); - var whenTrue = this.ParsePossibleRefExpression(forceConditionalAccessExpression); + var whenTrue = this.ParsePossibleRefExpression(); if (this.CurrentToken.Kind != SyntaxKind.ColonToken && - !forceConditionalAccessExpression && + !this.ForceConditionalAccessExpression && containsTernaryCollectionToReinterpret(whenTrue)) { // Keep track of where we are right now in case the new parse doesn't make things better. @@ -10691,7 +10691,9 @@ private ExpressionSyntax ParseExpressionContinued( afterQuestionToken.Reset(); // try reparsing with `?[` as a conditional access, not a ternary+collection - var newWhenTrue = this.ParsePossibleRefExpression(forceConditionalAccessExpression: true); + this.ForceConditionalAccessExpression = true; + var newWhenTrue = this.ParsePossibleRefExpression(); + this.ForceConditionalAccessExpression = false; if (this.CurrentToken.Kind == SyntaxKind.ColonToken) { @@ -10726,7 +10728,7 @@ private ExpressionSyntax ParseExpressionContinued( questionToken, whenTrue, this.EatToken(SyntaxKind.ColonToken), - this.ParsePossibleRefExpression(forceConditionalAccessExpression)); + this.ParsePossibleRefExpression()); } static bool containsTernaryCollectionToReinterpret(ExpressionSyntax expression) @@ -10783,8 +10785,8 @@ private ExpressionSyntax ParseIsExpression(ExpressionSyntax leftOperand, SyntaxT }; } - private ExpressionSyntax ParseTerm(Precedence precedence, bool forceConditionalAccessExpression) - => this.ParsePostFixExpression(ParseTermWithoutPostfix(precedence), forceConditionalAccessExpression); + private ExpressionSyntax ParseTerm(Precedence precedence) + => this.ParsePostFixExpression(ParseTermWithoutPostfix(precedence)); private ExpressionSyntax ParseTermWithoutPostfix(Precedence precedence) { @@ -11021,8 +11023,7 @@ private bool IsPossibleAnonymousMethodExpression() this.PeekToken(tokenIndex + 1).Kind != SyntaxKind.AsteriskToken; } - private ExpressionSyntax ParsePostFixExpression( - ExpressionSyntax expr, bool forceConditionalAccessExpression) + private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr) { Debug.Assert(expr != null); @@ -11088,12 +11089,12 @@ private ExpressionSyntax ParsePostFixExpression( continue; case SyntaxKind.QuestionToken: - if (CanStartConsequenceExpression(forceConditionalAccessExpression)) + if (CanStartConsequenceExpression()) { expr = _syntaxFactory.ConditionalAccessExpression( expr, this.EatToken(), - ParseConsequenceSyntax(forceConditionalAccessExpression)); + ParseConsequenceSyntax()); continue; } @@ -11109,7 +11110,7 @@ private ExpressionSyntax ParsePostFixExpression( } } - private bool CanStartConsequenceExpression(bool forceConditionalAccessExpression) + private bool CanStartConsequenceExpression() { Debug.Assert(this.CurrentToken.Kind == SyntaxKind.QuestionToken); var nextTokenKind = this.PeekToken(1).Kind; @@ -11123,7 +11124,7 @@ private bool CanStartConsequenceExpression(bool forceConditionalAccessExpression // could simply be `x?[0]`, or could be `x ? [0] : [1]`. // Caller only wants us to parse ?[ how it was originally parsed before collection expressions. - if (forceConditionalAccessExpression) + if (this.ForceConditionalAccessExpression) return true; using var _ = GetDisposableResetPoint(resetOnDispose: true); @@ -11141,7 +11142,7 @@ private bool CanStartConsequenceExpression(bool forceConditionalAccessExpression return false; } - internal ExpressionSyntax ParseConsequenceSyntax(bool forceConditionalAccessExpression) + internal ExpressionSyntax ParseConsequenceSyntax() { Debug.Assert(this.CurrentToken.Kind is SyntaxKind.DotToken or SyntaxKind.OpenBracketToken); ExpressionSyntax expr = this.CurrentToken.Kind switch @@ -11176,11 +11177,11 @@ internal ExpressionSyntax ParseConsequenceSyntax(bool forceConditionalAccessExpr continue; case SyntaxKind.QuestionToken: - return CanStartConsequenceExpression(forceConditionalAccessExpression) + return CanStartConsequenceExpression() ? _syntaxFactory.ConditionalAccessExpression( expr, operatorToken: this.EatToken(), - ParseConsequenceSyntax(forceConditionalAccessExpression)) + ParseConsequenceSyntax()) : expr; default: @@ -13239,6 +13240,12 @@ private bool IsInAsync } } + private bool ForceConditionalAccessExpression + { + get => _syntaxFactoryContext.ForceConditionalAccessExpression; + set => _syntaxFactoryContext.ForceConditionalAccessExpression = value; + } + private bool IsInQuery { get { return _syntaxFactoryContext.IsInQuery; } @@ -13387,6 +13394,7 @@ private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose) base.GetResetPoint(), _termState, _syntaxFactoryContext.IsInAsync, + _syntaxFactoryContext.ForceConditionalAccessExpression, _syntaxFactoryContext.QueryDepth); } @@ -13394,6 +13402,7 @@ private void Reset(ref ResetPoint state) { _termState = state.TerminatorState; _syntaxFactoryContext.IsInAsync = state.IsInAsync; + _syntaxFactoryContext.ForceConditionalAccessExpression = state.ForceConditionalAccessExpression; _syntaxFactoryContext.QueryDepth = state.QueryDepth; base.Reset(ref state.BaseResetPoint); } @@ -13433,17 +13442,20 @@ public void Dispose() internal SyntaxParser.ResetPoint BaseResetPoint; internal readonly TerminatorState TerminatorState; internal readonly bool IsInAsync; + internal readonly bool ForceConditionalAccessExpression; internal readonly int QueryDepth; internal ResetPoint( SyntaxParser.ResetPoint resetPoint, TerminatorState terminatorState, bool isInAsync, + bool forceConditionalAccessExpression, int queryDepth) { this.BaseResetPoint = resetPoint; this.TerminatorState = terminatorState; this.IsInAsync = isInAsync; + this.ForceConditionalAccessExpression = forceConditionalAccessExpression; this.QueryDepth = queryDepth; } } diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs index 534f37ad24f22..126b9975a86c2 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs @@ -24,6 +24,12 @@ internal class SyntaxFactoryContext /// internal bool IsInAsync; + /// + /// If we are forcing that ?[ is parsed as a conditional-access-expression, and not a conditional-expression + /// with a collection-expression in it. + /// + internal bool ForceConditionalAccessExpression; + internal int QueryDepth; /// From 78a5d38294a0ec9aee3e1265930ab7171087bce7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:56:47 -0700 Subject: [PATCH 06/13] Update src/Compilers/CSharp/Portable/Parser/LanguageParser.cs --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 4c8b42d4b9350..865161ee92754 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -10467,8 +10467,7 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence) return ParseExpressionContinued(leftOperand, precedence); } - private ExpressionSyntax ParseExpressionContinued( - ExpressionSyntax leftOperand, Precedence precedence) + private ExpressionSyntax ParseExpressionContinued(ExpressionSyntax leftOperand, Precedence precedence) { while (true) { From 4f76812533616b6eaee1b03797628737557f85a8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 21:57:41 -0700 Subject: [PATCH 07/13] invert --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 4c8b42d4b9350..5dc465be904f5 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -11177,12 +11177,12 @@ internal ExpressionSyntax ParseConsequenceSyntax() continue; case SyntaxKind.QuestionToken: - return CanStartConsequenceExpression() - ? _syntaxFactory.ConditionalAccessExpression( + return !CanStartConsequenceExpression() + ? expr + : _syntaxFactory.ConditionalAccessExpression( expr, operatorToken: this.EatToken(), - ParseConsequenceSyntax()) - : expr; + ParseConsequenceSyntax()); default: return expr; From 87703fc935079f40969b82dda28b1871e91bd0b6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Jun 2023 23:55:19 -0700 Subject: [PATCH 08/13] Simplify --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index a576f0a49871d..0b5fc021b408d 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -13393,7 +13393,6 @@ private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose) base.GetResetPoint(), _termState, _syntaxFactoryContext.IsInAsync, - _syntaxFactoryContext.ForceConditionalAccessExpression, _syntaxFactoryContext.QueryDepth); } @@ -13401,7 +13400,6 @@ private void Reset(ref ResetPoint state) { _termState = state.TerminatorState; _syntaxFactoryContext.IsInAsync = state.IsInAsync; - _syntaxFactoryContext.ForceConditionalAccessExpression = state.ForceConditionalAccessExpression; _syntaxFactoryContext.QueryDepth = state.QueryDepth; base.Reset(ref state.BaseResetPoint); } @@ -13441,20 +13439,17 @@ public void Dispose() internal SyntaxParser.ResetPoint BaseResetPoint; internal readonly TerminatorState TerminatorState; internal readonly bool IsInAsync; - internal readonly bool ForceConditionalAccessExpression; internal readonly int QueryDepth; internal ResetPoint( SyntaxParser.ResetPoint resetPoint, TerminatorState terminatorState, bool isInAsync, - bool forceConditionalAccessExpression, int queryDepth) { this.BaseResetPoint = resetPoint; this.TerminatorState = terminatorState; this.IsInAsync = isInAsync; - this.ForceConditionalAccessExpression = forceConditionalAccessExpression; this.QueryDepth = queryDepth; } } From e9bd97d1ed8919bdb313750b7642da6287d75391 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 24 Jun 2023 18:45:01 -0700 Subject: [PATCH 09/13] Reset when we go into a new scope --- .../CSharp/Portable/Parser/LanguageParser.cs | 14 + .../Parsing/CollectionLiteralParsingTests.cs | 265 ++++++++++++++++++ 2 files changed, 279 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 0b5fc021b408d..99d25765f84f4 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -12547,8 +12547,15 @@ private ExpressionSyntax ParseRegularStackAllocExpression() private AnonymousMethodExpressionSyntax ParseAnonymousMethodExpression() { var parentScopeIsInAsync = this.IsInAsync; + + var parentScopeForceConditionalAccess = this.ForceConditionalAccessExpression; + this.ForceConditionalAccessExpression = false; + var result = parseAnonymousMethodExpressionWorker(); + + this.ForceConditionalAccessExpression = parentScopeForceConditionalAccess; this.IsInAsync = parentScopeIsInAsync; + return result; AnonymousMethodExpressionSyntax parseAnonymousMethodExpressionWorker() @@ -12666,8 +12673,15 @@ private LambdaExpressionSyntax ParseLambdaExpression() { var attributes = ParseAttributeDeclarations(inExpressionContext: true); var parentScopeIsInAsync = this.IsInAsync; + + var parentScopeForceConditionalAccess = this.ForceConditionalAccessExpression; + this.ForceConditionalAccessExpression = false; + var result = parseLambdaExpressionWorker(); + + this.ForceConditionalAccessExpression = parentScopeForceConditionalAccess; this.IsInAsync = parentScopeIsInAsync; + return result; LambdaExpressionSyntax parseLambdaExpressionWorker() diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs index 96f98f47e33b6..a4cedaebed41b 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs @@ -2927,6 +2927,271 @@ public void ConditionalAmbiguity7A() EOF(); } + [Fact] + public void ConditionalAmbiguity8() + { + UsingExpression("a ? b?[() => { var v = x ? [y] : z; }] : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "v"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity9() + { + UsingExpression("a ? b?[delegate { var v = x ? [y] : z; }] : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.AnonymousMethodExpression); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "var"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "v"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity10() + { + UsingExpression("a ? b?[() => x ? [y] : z] : d"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.ParenthesizedLambdaExpression); + { + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "z"); + } + } + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + EOF(); + } + [Fact] public void CastVersusIndexAmbiguity1() { From d5dad665c06a2635e73599a4996777fc1be1493d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 24 Jun 2023 19:09:47 -0700 Subject: [PATCH 10/13] Add test cases --- .../Parsing/CollectionLiteralParsingTests.cs | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs index a4cedaebed41b..eddcd4e4f0efa 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs @@ -3192,6 +3192,262 @@ public void ConditionalAmbiguity10() EOF(); } + [Fact] + public void ConditionalAmbiguity11() + { + UsingExpression("a ? b?[c] : d ? e?[f] : g"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity12() + { + UsingExpression("a ? b?[c] : d ? e ? f?[g] : h : i"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "h"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "i"); + } + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity13() + { + UsingExpression("a ? b?[c] : d ? e ? f?[g] : h : i : j"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "h"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "i"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "j"); + } + } + } + EOF(); + } + [Fact] public void CastVersusIndexAmbiguity1() { From 6ac1f0e48cf531a4d1057f5c82ba513ac41d81e8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 24 Jun 2023 19:35:50 -0700 Subject: [PATCH 11/13] Add test cases --- .../Parsing/CollectionLiteralParsingTests.cs | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs index eddcd4e4f0efa..b2ed9bb0e7057 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CollectionLiteralParsingTests.cs @@ -3270,6 +3270,101 @@ public void ConditionalAmbiguity11() [Fact] public void ConditionalAmbiguity12() + { + UsingExpression("a ? b?[c] : d ? e ? f?[g] : h", + // (1,30): error CS1003: Syntax error, ':' expected + // a ? b?[c] : d ? e ? f?[g] : h + Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments(":").WithLocation(1, 30), + // (1,30): error CS1733: Expected expression + // a ? b?[c] : d ? e ? f?[g] : h + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 30)); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ElementBindingExpression); + { + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "h"); + } + } + M(SyntaxKind.ColonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity12A() { UsingExpression("a ? b?[c] : d ? e ? f?[g] : h : i"); @@ -3448,6 +3543,195 @@ public void ConditionalAmbiguity13() EOF(); } + [Fact] + public void ConditionalAmbiguity14() + { + UsingExpression("a ? b?[c] : d ? e ? f?[g] : h : i : j : k"); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "h"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "i"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "j"); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "k"); + } + } + EOF(); + } + + [Fact] + public void ConditionalAmbiguity15() + { + UsingExpression("a ? b?[c] : d ? e ? f?[g] : h : i : j : k : m", + // (1,1): error CS1073: Unexpected token ':' + // a ? b?[c] : d ? e ? f?[g] : h : i : j : k : m + Diagnostic(ErrorCode.ERR_UnexpectedToken, "a ? b?[c] : d ? e ? f?[g] : h : i : j : k").WithArguments(":").WithLocation(1, 1)); + + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "d"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.ConditionalExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "f"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.ExpressionElement); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "g"); + } + } + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "h"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "i"); + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "j"); + } + } + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "k"); + } + } + EOF(); + } + [Fact] public void CastVersusIndexAmbiguity1() { From 5fa3fca726906580104c092e78f1d88b52dff5e9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 27 Jun 2023 14:14:55 -0700 Subject: [PATCH 12/13] Add incremental test --- .../IncrementalParsingTests.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs index b838c01868071..20a8d40fad54b 100644 --- a/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/IncrementalParsing/IncrementalParsingTests.cs @@ -536,6 +536,54 @@ void M() WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); } + [Fact] + public void TestLocalFunctionCollectionVsAccessParsing() + { + var source = """ + using System; + + class C + { + void M() + { + var v = a ? b?[() => + { + var v = whatever(); + int LocalFunc() + { + var v = a ? [b] : c; + } + var v = whatever(); + }] : d; + } + } + """; + var tree = SyntaxFactory.ParseSyntaxTree(source); + Assert.Empty(tree.GetDiagnostics()); + + var localFunc1 = tree.GetRoot().DescendantNodesAndSelf().Single(n => n is LocalFunctionStatementSyntax); + var innerConditionalExpr1 = localFunc1.DescendantNodesAndSelf().Single(n => n is ConditionalExpressionSyntax); + + var text = tree.GetText(); + + var prefix = "var v = a ? b?[() =>"; + var suffix = "] : d;"; + + var prefixSpan = new TextSpan(source.IndexOf(prefix), length: prefix.Length); + var suffixSpan = new TextSpan(source.IndexOf(suffix), length: suffix.Length); + text = text.WithChanges(new TextChange(prefixSpan, ""), new TextChange(suffixSpan, "")); + tree = tree.WithChangedText(text); + Assert.Empty(tree.GetDiagnostics()); + + var fullTree = SyntaxFactory.ParseSyntaxTree(text.ToString()); + Assert.Empty(fullTree.GetDiagnostics()); + + var localFunc2 = tree.GetRoot().DescendantNodesAndSelf().Single(n => n is LocalFunctionStatementSyntax); + var innerConditionalExpr2 = localFunc2.DescendantNodesAndSelf().Single(n => n is ConditionalExpressionSyntax); + + WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); + } + #region "Regression" #if false From b6281485cd0fba17136d508dc4ff3ad53332cb9b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 27 Jun 2023 14:41:04 -0700 Subject: [PATCH 13/13] Add docs --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 99d25765f84f4..ba7185060cd23 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -10745,6 +10745,10 @@ static bool containsTernaryCollectionToReinterpret(ExpressionSyntax expression) return true; } + // Note: we could consider not recursing into anonymous-methods/lambdas (since we reset the + // ForceConditionalAccessExpression flag when we go into that). However, that adds a bit of + // fragile coupling between these different code blocks that i'd prefer to avoid. In practice + // the extra cost here will almost never occur, so the simplicity is worth it. foreach (var child in current.ChildNodesAndTokens()) stack.Push(child); }