Skip to content

Commit

Permalink
Parse unchecked gracefully in operators (#61309)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored May 19, 2022
1 parent d7d1d04 commit 5b9a9d4
Show file tree
Hide file tree
Showing 21 changed files with 535 additions and 10 deletions.
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7094,4 +7094,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureRelaxedShiftOperator" xml:space="preserve">
<value>relaxed shift operator</value>
</data>
<data name="ERR_MisplacedUnchecked" xml:space="preserve">
<value>Unexpected keyword 'unchecked'</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2068,6 +2068,7 @@ internal enum ErrorCode

ERR_CannotBeConvertedToUTF8 = 9026,
ERR_ExpressionTreeContainsUTF8StringLiterals = 9027,
ERR_MisplacedUnchecked = 9028,

#endregion

Expand Down
21 changes: 17 additions & 4 deletions src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ private OperatorMemberCrefSyntax ParseOperatorMemberCref()
{
Debug.Assert(CurrentToken.Kind == SyntaxKind.OperatorKeyword);
SyntaxToken operatorKeyword = EatToken();
SyntaxToken checkedKeyword = TryEatCheckedKeyword(isConversion: false);
SyntaxToken checkedKeyword = TryEatCheckedKeyword(isConversion: false, ref operatorKeyword);

SyntaxToken operatorToken;

Expand Down Expand Up @@ -1113,9 +1113,9 @@ CurrentToken.Kind is (SyntaxKind.GreaterThanToken or SyntaxKind.GreaterThanEqual
return SyntaxFactory.OperatorMemberCref(operatorKeyword, checkedKeyword, operatorToken, parameters);
}

private SyntaxToken TryEatCheckedKeyword(bool isConversion)
private SyntaxToken TryEatCheckedKeyword(bool isConversion, ref SyntaxToken operatorKeyword)
{
SyntaxToken checkedKeyword = TryEatToken(SyntaxKind.CheckedKeyword); // https://github.com/dotnet/roslyn/issues/60394 : consider gracefully recovering from erroneous use of 'unchecked' at this location
SyntaxToken checkedKeyword = tryEatCheckedOrHandleUnchecked(ref operatorKeyword);

if (checkedKeyword is not null &&
(isConversion || SyntaxFacts.IsAnyOverloadableOperator(CurrentToken.Kind)))
Expand All @@ -1124,6 +1124,19 @@ private SyntaxToken TryEatCheckedKeyword(bool isConversion)
}

return checkedKeyword;

SyntaxToken tryEatCheckedOrHandleUnchecked(ref SyntaxToken operatorKeyword)
{
if (CurrentToken.Kind == SyntaxKind.UncheckedKeyword)
{
// if we encounter `operator unchecked`, we place the `unchecked` as skipped trivia on `operator`
var misplacedToken = this.AddError(this.EatToken(), ErrorCode.ERR_MisplacedUnchecked);
operatorKeyword = AddTrailingSkippedSyntax(operatorKeyword, misplacedToken);
return null;
}

return TryEatToken(SyntaxKind.CheckedKeyword);
}
}

/// <summary>
Expand All @@ -1136,7 +1149,7 @@ private ConversionOperatorMemberCrefSyntax ParseConversionOperatorMemberCref()
SyntaxToken implicitOrExplicit = EatToken();

SyntaxToken operatorKeyword = EatToken(SyntaxKind.OperatorKeyword);
SyntaxToken checkedKeyword = TryEatCheckedKeyword(isConversion: true);
SyntaxToken checkedKeyword = TryEatCheckedKeyword(isConversion: true, ref operatorKeyword);

TypeSyntax type = ParseCrefType(typeArgumentsMustBeIdentifiers: false);

Expand Down
19 changes: 16 additions & 3 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3348,7 +3348,7 @@ private ConversionOperatorDeclarationSyntax TryParseConversionOperatorDeclaratio
{
possibleConversion = false;
}
else if (this.PeekToken(1).Kind == SyntaxKind.CheckedKeyword) // https://github.com/dotnet/roslyn/issues/60394 : consider gracefully recovering from erroneous use of 'unchecked' at this location
else if (this.PeekToken(1).Kind is SyntaxKind.CheckedKeyword or SyntaxKind.UncheckedKeyword)
{
possibleConversion = !SyntaxFacts.IsAnyOverloadableOperator(this.PeekToken(2).Kind);
}
Expand Down Expand Up @@ -3392,7 +3392,7 @@ private ConversionOperatorDeclarationSyntax TryParseConversionOperatorDeclaratio
}

opKeyword = this.EatToken(SyntaxKind.OperatorKeyword);
var checkedKeyword = this.TryEatToken(SyntaxKind.CheckedKeyword); // https://github.com/dotnet/roslyn/issues/60394 : consider gracefully recovering from erroneous use of 'unchecked' at this location
var checkedKeyword = TryEatCheckedOrHandleUnchecked(ref opKeyword);

this.Release(ref point);
point = GetResetPoint();
Expand Down Expand Up @@ -3510,14 +3510,27 @@ ExplicitInterfaceSpecifierSyntax tryParseExplicitInterfaceSpecifier()
}
}

private SyntaxToken TryEatCheckedOrHandleUnchecked(ref SyntaxToken operatorKeyword)
{
if (CurrentToken.Kind == SyntaxKind.UncheckedKeyword)
{
// if we encounter `operator unchecked`, we place the `unchecked` as skipped trivia on `operator`
var misplacedToken = this.AddError(this.EatToken(), ErrorCode.ERR_MisplacedUnchecked);
operatorKeyword = AddTrailingSkippedSyntax(operatorKeyword, misplacedToken);
return null;
}

return TryEatToken(SyntaxKind.CheckedKeyword);
}

private OperatorDeclarationSyntax ParseOperatorDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt)
{
var opKeyword = this.EatToken(SyntaxKind.OperatorKeyword);
var checkedKeyword = this.TryEatToken(SyntaxKind.CheckedKeyword); // https://github.com/dotnet/roslyn/issues/60394 : consider gracefully recovering from erroneous use of 'unchecked' at this location
var checkedKeyword = TryEatCheckedOrHandleUnchecked(ref opKeyword);
SyntaxToken opToken;
int opTokenErrorOffset;
int opTokenErrorWidth;
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,6 @@ protected TNode CheckFeatureAvailability<TNode>(TNode node, MessageID feature, b
where TNode : GreenNode
{
LanguageVersion availableVersion = this.Options.LanguageVersion;
LanguageVersion requiredVersion = feature.RequiredVersion();

// There are special error codes for some features, so handle those separately.
switch (feature)
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1587,7 +1587,7 @@ public static SyntaxTriviaList ParseLeadingTrivia(string text, int offset = 0)
/// <summary>
/// Parse a list of trivia rules for leading trivia.
/// </summary>
internal static SyntaxTriviaList ParseLeadingTrivia(string text, CSharpParseOptions? options, int offset = 0)
internal static SyntaxTriviaList ParseLeadingTrivia(string text, CSharpParseOptions options, int offset = 0)
{
using (var lexer = new InternalSyntax.Lexer(MakeSourceText(text, offset), options))
{
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5b9a9d4

Please sign in to comment.