From 5b9a9d44eee29f433d6c9ef9740642dd4d08e963 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 18 May 2022 18:10:06 -0700 Subject: [PATCH] Parse `unchecked` gracefully in operators (#61309) --- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Parser/DocumentationCommentParser.cs | 21 +- .../CSharp/Portable/Parser/LanguageParser.cs | 19 +- .../CSharp/Portable/Parser/SyntaxParser.cs | 1 - .../CSharp/Portable/Syntax/SyntaxFactory.cs | 2 +- .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Test/Syntax/Parsing/CrefParsingTests.cs | 263 +++++++++++++++++- .../Parsing/MemberDeclarationParsingTests.cs | 170 +++++++++++ 21 files changed, 535 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2640bf46255ad..3f0abdc1a01db 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7094,4 +7094,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ relaxed shift operator + + Unexpected keyword 'unchecked' + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index d4971c7e8663e..c224cb0cf6b0c 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2068,6 +2068,7 @@ internal enum ErrorCode ERR_CannotBeConvertedToUTF8 = 9026, ERR_ExpressionTreeContainsUTF8StringLiterals = 9027, + ERR_MisplacedUnchecked = 9028, #endregion diff --git a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs index 6d8180407dbcf..f2e0568ba2400 100644 --- a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs @@ -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; @@ -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))) @@ -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); + } } /// @@ -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); diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 18d541d157515..488028988c542 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -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); } @@ -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(); @@ -3510,6 +3510,19 @@ 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 attributes, SyntaxListBuilder modifiers, @@ -3517,7 +3530,7 @@ private OperatorDeclarationSyntax ParseOperatorDeclaration( 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; diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs index 0d571c0fb58eb..aebaae650b02d 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs @@ -1086,7 +1086,6 @@ protected TNode CheckFeatureAvailability(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) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index eb9269263f2a1..0ec3762eed18e 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -1587,7 +1587,7 @@ public static SyntaxTriviaList ParseLeadingTrivia(string text, int offset = 0) /// /// Parse a list of trivia rules for leading trivia. /// - 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)) { diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index aee8d92be6a47..069e6d60cdd74 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -847,6 +847,11 @@ Vzory řezů se dají použít jenom jednou a přímo uvnitř vzoru seznamu. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Skupina metod se nedá převést na ukazatel na funkci (nechybí &)? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 5e017286f6b78..aa17e19b2c877 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -847,6 +847,11 @@ Segmentmuster dürfen nur einmal und direkt innerhalb eines Listenmusters verwendet werden. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Die Methodengruppe kann nicht in den Funktionszeiger konvertiert werden (fehlt ein "&"?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 69325f77ca71b..9f3b2528aca48 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -847,6 +847,11 @@ Los patrones de segmento solo se pueden utilizar una vez directamente dentro de un patrón de lista. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) No se puede convertir el grupo de métodos en puntero de función (¿falta un operador "&"?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index e52fbcd8b6031..af853e5d4fa3b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -847,6 +847,11 @@ Les modèles de tranche ne peuvent être utilisés qu'une seule fois et directement à l'intérieur d'un modèle de liste. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Impossible de convertir le groupe de méthodes en pointeur de fonction (manque-t-il un '&' ?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 4a0285271f1a6..eae7a06d784c5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -847,6 +847,11 @@ I modelli di sezione possono essere usati solo una volta e direttamente all'interno di un modello di elenco. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Non è possibile convertire il gruppo di metodi nel puntatore a funzione. Manca un operatore '&'? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 92cf8d2ca6e8f..90756311001ad 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -847,6 +847,11 @@ スライス パターンは、1 回限り、リスト パターン内で直接使用される可能性があります。 + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) メソッド グループを関数ポインターに変換できません ('&' が抜けていないか確認してください) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index f8e8d56c302b6..1c6fa28c47284 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -847,6 +847,11 @@ 조각 패턴은 목록 패턴 내에서 바로 한 번만 사용할 수 있습니다. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) 메서드 그룹을 함수 포인터로 변환할 수 없습니다. ('&'가 누락되었습니까?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index eade8c0332949..c466ad6461524 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -847,6 +847,11 @@ Wzorce wycinków mogą być używane tylko raz i bezpośrednio wewnątrz wzorca listy. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Nie można przekonwertować grupy metod na wskaźnik funkcji (może brakuje znaku „&”?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 4c2c413af0111..31bd8b67a3c62 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -847,6 +847,11 @@ Os padrões de fatia somente podem ser usados uma vez e diretamente dentro de um padrão de lista. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Não é possível converter o grupo de métodos no ponteiro de função (Está faltando um '&'?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 57ad39b81ed9b..02c4c588a718a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -847,6 +847,11 @@ Шаблоны среза можно использовать только один раз и непосредственно внутри шаблона списка. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Не удается преобразовать группу методов в указатель на функцию. (Возможно, пропущен "&"?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index d9f63bb2ef77d..fcde4c81f26eb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -847,6 +847,11 @@ Dilim desenleri yalnızca bir kez ve doğrudan bir liste deseninin içinde kullanılabilir. + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) Metot grubu işlev işaretçisine dönüştürülemiyor ('&' eksik mi?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 438c92a30702b..b8cb8f131c86d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -847,6 +847,11 @@ 切片模式只能使用一次,并且直接在列表模式内使用。 + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) 无法将方法组转换为函数指针(是否缺少 "&"?) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 450f5e51e2485..66e5082fd93ec 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -847,6 +847,11 @@ 切片模式只能在清單模式中使用一次且直接使用。 + + Unexpected keyword 'unchecked' + Unexpected keyword 'unchecked' + + Cannot convert method group to function pointer (Are you missing a '&'?) 無法將方法群組轉換成函式指標 (您是否缺少 '&'?) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs index d9cd69d8a959c..0bc93b66d45ff 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs @@ -26,7 +26,7 @@ protected override SyntaxTree ParseTree(string text, CSharpParseOptions options) protected override CSharpSyntaxNode ParseNode(string text, CSharpParseOptions options) { var commentText = string.Format(@"/// ", text); - var trivia = SyntaxFactory.ParseLeadingTrivia(commentText).Single(); + var trivia = SyntaxFactory.ParseLeadingTrivia(commentText, options ?? CSharpParseOptions.Default).Single(); var structure = (DocumentationCommentTriviaSyntax)trivia.GetStructure(); var attr = structure.DescendantNodes().OfType().Single(); return attr.Cref; @@ -432,6 +432,25 @@ public void UnqualifiedOperatorMember1_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void UnqualifiedOperatorMember1_Unchecked() + { + UsingNode("operator unchecked +", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'operator unchecked +' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "operator unchecked +").WithArguments("operator unchecked +").WithLocation(1, 16), + // (1,25): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 25)); + + N(SyntaxKind.OperatorMemberCref); + { + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.PlusToken); + } + EOF(); + } + [Fact] public void UnqualifiedOperatorMember2() { @@ -481,6 +500,37 @@ public void UnqualifiedOperatorMember2_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void UnqualifiedOperatorMember2_Unchecked() + { + UsingNode("operator unchecked +(A)", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'operator unchecked +(A)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "operator unchecked +(A)").WithArguments("operator unchecked +(A)").WithLocation(1, 16), + // (1,25): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 25)); + + N(SyntaxKind.OperatorMemberCref); + { + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.PlusToken); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + #endregion Unqualified #region Qualified @@ -526,6 +576,34 @@ public void QualifiedOperatorMember1_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void QualifiedOperatorMember1_Unchecked() + { + UsingNode("T.operator unchecked +", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'T.operator unchecked +' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "T.operator unchecked +").WithArguments("T.operator unchecked +").WithLocation(1, 16), + // (1,27): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 27) + ); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.OperatorMemberCref); + { + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.PlusToken); + } + } + EOF(); + } + [Fact] public void QualifiedOperatorMember2() { @@ -683,6 +761,56 @@ public void GreaterThanGreaterThan_Checked() EOF(); } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void GreaterThanGreaterThan_Unchecked() + { + UsingNode("operator unchecked }}(A{A{T}})", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'operator unchecked }}(A{A{T}})' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "operator unchecked }}(A{A{T}})").WithArguments("operator unchecked }}(A{A{T}})").WithLocation(1, 16), + // (1,25): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 25) + ); + + N(SyntaxKind.OperatorMemberCref); + { + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.GreaterThanGreaterThanToken); + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "A"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "A"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + [WorkItem(546992, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546992")] [Fact] public void GreaterThanGreaterThanGreaterThan() @@ -768,6 +896,30 @@ public void UnqualifiedConversionOperatorMember1_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void UnqualifiedConversionOperatorMember1_Unchecked() + { + UsingNode("implicit operator unchecked A", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'implicit operator unchecked A' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "implicit operator unchecked A").WithArguments("implicit operator unchecked A").WithLocation(1, 16), + // (1,34): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 34) + ); + + N(SyntaxKind.ConversionOperatorMemberCref); + { + N(SyntaxKind.ImplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + EOF(); + } + [Fact] public void UnqualifiedConversionOperatorMember2() { @@ -825,6 +977,41 @@ public void UnqualifiedConversionOperatorMember2_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void UnqualifiedConversionOperatorMember2_Unchecked() + { + UsingNode("explicit operator unchecked A(B)", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'explicit operator unchecked A(B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "explicit operator unchecked A(B)").WithArguments("explicit operator unchecked A(B)").WithLocation(1, 16), + // (1,34): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 34) + ); + + N(SyntaxKind.ConversionOperatorMemberCref); + { + N(SyntaxKind.ExplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + #endregion Unqualified #region Qualified @@ -878,6 +1065,37 @@ public void QualifiedConversionOperatorMember1_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void QualifiedConversionOperatorMember1_Unchecked() + { + UsingNode("T.implicit operator unchecked A", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'T.implicit operator unchecked A' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "T.implicit operator unchecked A").WithArguments("T.implicit operator unchecked A").WithLocation(1, 16), + // (1,36): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 36) + ); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ConversionOperatorMemberCref); + { + N(SyntaxKind.ImplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + } + } + [Fact] public void QualifiedConversionOperatorMember2() { @@ -951,6 +1169,49 @@ public void QualifiedConversionOperatorMember2_Checked() } } + [Fact, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + public void QualifiedConversionOperatorMember2_Unchecked() + { + UsingNode("T.explicit operator unchecked A(B)", TestOptions.RegularWithDocumentationComments, + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'T.explicit operator unchecked A(B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "T.explicit operator unchecked A(B)").WithArguments("T.explicit operator unchecked A(B)").WithLocation(1, 16), + // (1,36): error CS9028: Unexpected keyword 'unchecked' + // /// + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 36) + ); + + N(SyntaxKind.QualifiedCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.ConversionOperatorMemberCref); + { + N(SyntaxKind.ExplicitKeyword); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + } + #endregion Qualified #endregion Conversion Operator Members diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index 987f859c6c482..ddf30e75fbe8a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -8027,5 +8027,175 @@ public void CheckedOperatorDeclaration_06(string op, SyntaxKind opToken) } EOF(); } + + [Theory, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + [InlineData("+", SyntaxKind.PlusToken)] + [InlineData("-", SyntaxKind.MinusToken)] + [InlineData("!", SyntaxKind.ExclamationToken)] + [InlineData("~", SyntaxKind.TildeToken)] + [InlineData("++", SyntaxKind.PlusPlusToken)] + [InlineData("--", SyntaxKind.MinusMinusToken)] + [InlineData("true", SyntaxKind.TrueKeyword)] + [InlineData("false", SyntaxKind.FalseKeyword)] + public void UncheckedOperatorDeclaration_01(string op, SyntaxKind opToken) + { + UsingDeclaration("C operator unchecked " + op + "(C x) => x;", expectedErrors: + // (1,12): error CS9028: Unexpected keyword 'unchecked' + // C operator unchecked op(C x) => x; + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 12)); + + N(SyntaxKind.OperatorDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.OperatorKeyword); + N(opToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + + [Theory, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + [InlineData("+", SyntaxKind.PlusToken)] + [InlineData("-", SyntaxKind.MinusToken)] + [InlineData("*", SyntaxKind.AsteriskToken)] + [InlineData("/", SyntaxKind.SlashToken)] + [InlineData("%", SyntaxKind.PercentToken)] + [InlineData("&", SyntaxKind.AmpersandToken)] + [InlineData("|", SyntaxKind.BarToken)] + [InlineData("^", SyntaxKind.CaretToken)] + [InlineData("<<", SyntaxKind.LessThanLessThanToken)] + [InlineData(">>", SyntaxKind.GreaterThanGreaterThanToken)] + [InlineData(">>>", SyntaxKind.GreaterThanGreaterThanGreaterThanToken)] + [InlineData("==", SyntaxKind.EqualsEqualsToken)] + [InlineData("!=", SyntaxKind.ExclamationEqualsToken)] + [InlineData(">", SyntaxKind.GreaterThanToken)] + [InlineData("<", SyntaxKind.LessThanToken)] + [InlineData(">=", SyntaxKind.GreaterThanEqualsToken)] + [InlineData("<=", SyntaxKind.LessThanEqualsToken)] + public void UncheckedOperatorDeclaration_04(string op, SyntaxKind opToken) + { + UsingDeclaration("C I.operator unchecked " + op + "(C x, C y) => x;", options: TestOptions.RegularPreview, + // (1,14): error CS9028: Unexpected keyword 'unchecked' + // C I.operator unchecked op(C x, C y) => x; + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 14)); + + N(SyntaxKind.OperatorDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.ExplicitInterfaceSpecifier); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "I"); + } + N(SyntaxKind.DotToken); + } + N(SyntaxKind.OperatorKeyword); + N(opToken); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.IdentifierToken, "y"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + + [Theory, WorkItem(60394, "https://github.com/dotnet/roslyn/issues/60394")] + [InlineData("implicit", SyntaxKind.ImplicitKeyword)] + [InlineData("explicit", SyntaxKind.ExplicitKeyword)] + public void UnheckedOperatorDeclaration_05(string op, SyntaxKind opToken) + { + UsingDeclaration(op + " operator unchecked D(C x) => x;", expectedErrors: + // (1,19): error CS9028: Unexpected keyword 'unchecked' + // implicit operator unchecked op(C x) => x; + Diagnostic(ErrorCode.ERR_MisplacedUnchecked, "unchecked").WithLocation(1, 19)); + + N(SyntaxKind.ConversionOperatorDeclaration); + { + N(opToken); + N(SyntaxKind.OperatorKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "D"); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Parameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } } }