提交 db1f92d5 编写于 作者: N Neal Gafter 提交者: GitHub

Correct an off-by-one error in the precedence for is-pattern parsing. (#15917)

Fixes #15734
上级 e459e69d
...@@ -9208,6 +9208,9 @@ private bool IsAwaitExpression() ...@@ -9208,6 +9208,9 @@ private bool IsAwaitExpression()
return false; return false;
} }
/// <summary>
/// Parse a subexpression of the enclosing operator of the given precedence.
/// </summary>
private ExpressionSyntax ParseSubExpression(Precedence precedence) private ExpressionSyntax ParseSubExpression(Precedence precedence)
{ {
_recursionDepth++; _recursionDepth++;
...@@ -9434,7 +9437,7 @@ private ExpressionSyntax ParseThrowExpression() ...@@ -9434,7 +9437,7 @@ private ExpressionSyntax ParseThrowExpression()
private ExpressionSyntax ParseIsExpression(ExpressionSyntax leftOperand, SyntaxToken opToken) private ExpressionSyntax ParseIsExpression(ExpressionSyntax leftOperand, SyntaxToken opToken)
{ {
var node = this.ParseTypeOrPattern(); var node = this.ParseTypeOrPatternForIsOperator();
if (node is PatternSyntax) if (node is PatternSyntax)
{ {
var result = _syntaxFactory.IsPatternExpression(leftOperand, opToken, (PatternSyntax)node); var result = _syntaxFactory.IsPatternExpression(leftOperand, opToken, (PatternSyntax)node);
......
...@@ -7,13 +7,15 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax ...@@ -7,13 +7,15 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{ {
internal partial class LanguageParser : SyntaxParser internal partial class LanguageParser : SyntaxParser
{ {
// Priority is the TypeSyntax. It might return TypeSyntax which might be a constant pattern such as enum 'Days.Sunday' /// <summary>
// We handle such cases in the binder of is operator. /// Parses the type, or pattern, right-hand operand of an is expression.
// It is used for parsing patterns in the is operators. /// Priority is the TypeSyntax. It may return a TypeSyntax which turns out in binding to
private CSharpSyntaxNode ParseTypeOrPattern() /// be a constant pattern such as enum 'Days.Sunday'. We handle such cases in the binder of the is operator.
/// </summary>
private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
{ {
var tk = this.CurrentToken.Kind; var tk = this.CurrentToken.Kind;
CSharpSyntaxNode node = null; Precedence precedence = GetPrecedence(SyntaxKind.IsPatternExpression);
switch (tk) switch (tk)
{ {
...@@ -39,67 +41,40 @@ private CSharpSyntaxNode ParseTypeOrPattern() ...@@ -39,67 +41,40 @@ private CSharpSyntaxNode ParseTypeOrPattern()
{ {
TypeSyntax type = this.ParseType(ParseTypeMode.AfterIsOrCase); TypeSyntax type = this.ParseType(ParseTypeMode.AfterIsOrCase);
tk = this.CurrentToken.ContextualKind; if (!type.IsMissing && this.IsTrueIdentifier())
if (!type.IsMissing)
{ {
if (this.IsTrueIdentifier()) var designation = ParseSimpleDesignation();
{ return _syntaxFactory.DeclarationPattern(type, designation);
var designation = ParseSimpleDesignation();
node = _syntaxFactory.DeclarationPattern(type, designation);
}
} }
if (node == null) tk = this.CurrentToken.ContextualKind;
if ((!IsExpectedBinaryOperator(tk) || GetPrecedence(SyntaxFacts.GetBinaryExpression(tk)) <= precedence) &&
// member selection is not formally a binary operator but has higher precedence than relational
tk != SyntaxKind.DotToken)
{ {
Debug.Assert(Precedence.Shift == Precedence.Relational + 1); // it is a typical "is Type" operator.
if ((IsExpectedBinaryOperator(tk) && GetPrecedence(SyntaxFacts.GetBinaryExpression(tk)) > Precedence.Relational) || // Note that we don't bother checking for primary expressions such as X[e], X(e), X++, and X--
tk == SyntaxKind.DotToken) // member selection is not formally a binary operator but has higher precedence than relational // as those are never semantically valid constant expressions for a pattern
{ return type;
this.Reset(ref resetPoint);
// We parse a shift-expression ONLY (nothing looser) - i.e. not a relational expression
// So x is y < z should be parsed as (x is y) < z
// But x is y << z should be parsed as x is (y << z)
node = _syntaxFactory.ConstantPattern(this.ParseSubExpression(Precedence.Shift));
}
// it is a typical "is Type" operator
else
{
// Note that we don't bother checking for primary expressions such as X[e], X(e), X++, and X--
// as those are never semantically valid constant expressions for a pattern
node = type;
}
} }
this.Reset(ref resetPoint);
} }
finally finally
{ {
this.Release(ref resetPoint); this.Release(ref resetPoint);
} }
} }
else
{
// In places where a pattern is supported, we do not support tuple types
// due to both syntactic and semantic ambiguities between tuple types and positional patterns.
// But it still might be a pattern such as (operand is 3) or (operand is nameof(x))
node = _syntaxFactory.ConstantPattern(this.ParseExpressionCore());
}
return node;
}
// This method is used when we always want a pattern as a result. // We parse a shift-expression ONLY (nothing looser) - i.e. not a relational expression
// For instance, it is used in parsing recursivepattern and propertypattern. // So x is y < z should be parsed as (x is y) < z
// SubPatterns in these (recursivepattern, propertypattern) must be a type of Pattern. // But x is y << z should be parsed as x is (y << z)
private PatternSyntax ParsePattern() Debug.Assert(Precedence.Shift == precedence + 1);
{
var node = this.ParseExpressionOrPattern(whenIsKeyword: false);
if (node is PatternSyntax)
{
return (PatternSyntax)node;
}
Debug.Assert(node is ExpressionSyntax); // In places where a pattern is supported, we do not support tuple types
return _syntaxFactory.ConstantPattern((ExpressionSyntax)node); // due to both syntactic and semantic ambiguities between tuple types and positional patterns.
// But it still might be a pattern such as (operand is 3) or (operand is nameof(x))
return _syntaxFactory.ConstantPattern(this.ParseSubExpressionCore(precedence));
} }
// //
......
...@@ -56,6 +56,16 @@ internal void UsingStatement(string text, params DiagnosticDescription[] expecte ...@@ -56,6 +56,16 @@ internal void UsingStatement(string text, params DiagnosticDescription[] expecte
UsingNode(node); UsingNode(node);
} }
internal void UsingExpression(string text, params DiagnosticDescription[] expectedErrors)
{
var node = SyntaxFactory.ParseExpression(text);
// we validate the text roundtrips
Assert.Equal(text, node.ToFullString());
var actualErrors = node.GetDiagnostics();
actualErrors.Verify(expectedErrors);
UsingNode(node);
}
/// <summary> /// <summary>
/// Parses given string and initializes a depth-first preorder enumerator. /// Parses given string and initializes a depth-first preorder enumerator.
/// </summary> /// </summary>
......
...@@ -532,5 +532,379 @@ public void TypeDisambiguation_03() ...@@ -532,5 +532,379 @@ public void TypeDisambiguation_03()
} }
EOF(); EOF();
} }
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence00()
{
UsingExpression("A is B << C");
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.LeftShiftExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
N(SyntaxKind.LessThanLessThanToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "C");
}
}
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence01()
{
UsingExpression("A is 1 << 2");
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.LeftShiftExpression);
{
N(SyntaxKind.NumericLiteralExpression);
{
N(SyntaxKind.NumericLiteralToken, "1");
}
N(SyntaxKind.LessThanLessThanToken);
N(SyntaxKind.NumericLiteralExpression);
{
N(SyntaxKind.NumericLiteralToken, "2");
}
}
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence02()
{
UsingExpression("A is null < B");
N(SyntaxKind.LessThanExpression);
{
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
}
}
N(SyntaxKind.LessThanToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence02b()
{
UsingExpression("A is B < C");
N(SyntaxKind.LessThanExpression);
{
N(SyntaxKind.IsExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
N(SyntaxKind.LessThanToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "C");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence03()
{
UsingExpression("A is null == B");
N(SyntaxKind.EqualsExpression);
{
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
}
}
N(SyntaxKind.EqualsEqualsToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence04()
{
UsingExpression("A is null & B");
N(SyntaxKind.BitwiseAndExpression);
{
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
}
}
N(SyntaxKind.AmpersandToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence05()
{
UsingExpression("A is null && B");
N(SyntaxKind.LogicalAndExpression);
{
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
}
}
N(SyntaxKind.AmpersandAmpersandToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence05b()
{
UsingExpression("A is null || B");
N(SyntaxKind.LogicalOrExpression);
{
N(SyntaxKind.IsPatternExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "A");
}
N(SyntaxKind.IsKeyword);
N(SyntaxKind.ConstantPattern);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
}
}
N(SyntaxKind.BarBarToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
EOF();
}
[Fact, WorkItem(15734, "https://github.com/dotnet/roslyn/issues/15734")]
public void PatternExpressionPrecedence06()
{
UsingStatement(@"switch (e) {
case 1 << 2:
case B << C:
case null < B:
case null == B:
case null & B:
case null && B:
break;
}");
N(SyntaxKind.SwitchStatement);
{
N(SyntaxKind.SwitchKeyword);
N(SyntaxKind.OpenParenToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "e");
}
N(SyntaxKind.CloseParenToken);
N(SyntaxKind.OpenBraceToken);
N(SyntaxKind.SwitchSection);
{
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.LeftShiftExpression);
{
N(SyntaxKind.NumericLiteralExpression);
{
N(SyntaxKind.NumericLiteralToken, "1");
}
N(SyntaxKind.LessThanLessThanToken);
N(SyntaxKind.NumericLiteralExpression);
{
N(SyntaxKind.NumericLiteralToken, "2");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.LeftShiftExpression);
{
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
N(SyntaxKind.LessThanLessThanToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "C");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.LessThanExpression);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
N(SyntaxKind.LessThanToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.EqualsExpression);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
N(SyntaxKind.EqualsEqualsToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.BitwiseAndExpression);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
N(SyntaxKind.AmpersandToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.CaseSwitchLabel);
{
N(SyntaxKind.CaseKeyword);
N(SyntaxKind.LogicalAndExpression);
{
N(SyntaxKind.NullLiteralExpression);
{
N(SyntaxKind.NullKeyword);
}
N(SyntaxKind.AmpersandAmpersandToken);
N(SyntaxKind.IdentifierName);
{
N(SyntaxKind.IdentifierToken, "B");
}
}
N(SyntaxKind.ColonToken);
}
N(SyntaxKind.BreakStatement);
{
N(SyntaxKind.BreakKeyword);
N(SyntaxKind.SemicolonToken);
}
}
N(SyntaxKind.CloseBraceToken);
}
EOF();
}
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册