Index: clang/lib/Format/FormatToken.h =================================================================== --- clang/lib/Format/FormatToken.h +++ clang/lib/Format/FormatToken.h @@ -102,9 +102,11 @@ TYPE(TypenameMacro) \ TYPE(UnaryOperator) \ TYPE(CSharpStringLiteral) \ - TYPE(CSharpNullCoalescing) \ TYPE(CSharpNamedArgumentColon) \ - TYPE(CSharpNullableTypeQuestionMark) \ + TYPE(CSharpNullable) \ + TYPE(CSharpNullCoalescing) \ + TYPE(CSharpNullConditional) \ + TYPE(CSharpNullConditionalSq) \ TYPE(Unknown) enum TokenType { Index: clang/lib/Format/FormatTokenLexer.h =================================================================== --- clang/lib/Format/FormatTokenLexer.h +++ clang/lib/Format/FormatTokenLexer.h @@ -52,8 +52,8 @@ bool tryMergeJSPrivateIdentifier(); bool tryMergeCSharpStringLiteral(); bool tryMergeCSharpKeywordVariables(); - bool tryMergeCSharpNullConditionals(); bool tryMergeCSharpDoubleQuestion(); + bool tryMergeCSharpNullConditional(); bool tryTransformCSharpForEach(); bool tryMergeCSharpAttributeAndTarget(); Index: clang/lib/Format/FormatTokenLexer.cpp =================================================================== --- clang/lib/Format/FormatTokenLexer.cpp +++ clang/lib/Format/FormatTokenLexer.cpp @@ -84,7 +84,7 @@ return; if (tryMergeCSharpDoubleQuestion()) return; - if (tryMergeCSharpNullConditionals()) + if (tryMergeCSharpNullConditional()) return; if (tryTransformCSharpForEach()) return; @@ -319,7 +319,7 @@ auto &SecondQuestion = *(Tokens.end() - 1); if (!FirstQuestion->is(tok::question) || !SecondQuestion->is(tok::question)) return false; - FirstQuestion->Tok.setKind(tok::question); + FirstQuestion->Tok.setKind(tok::question); // no '??' in clang tokens. FirstQuestion->TokenText = StringRef(FirstQuestion->TokenText.begin(), SecondQuestion->TokenText.end() - FirstQuestion->TokenText.begin()); @@ -329,6 +329,32 @@ return true; } +// Merge '?[' and '?.' pairs into single tokens. +bool FormatTokenLexer::tryMergeCSharpNullConditional() { + if (Tokens.size() < 2) + return false; + auto &Question = *(Tokens.end() - 2); + auto &PeriodOrLSquare = *(Tokens.end() - 1); + if (!Question->is(tok::question) || + !PeriodOrLSquare->isOneOf(tok::l_square, tok::period)) + return false; + Question->TokenText = + StringRef(Question->TokenText.begin(), + PeriodOrLSquare->TokenText.end() - Question->TokenText.begin()); + Question->ColumnWidth += PeriodOrLSquare->ColumnWidth; + + if (PeriodOrLSquare->is(tok::l_square)) { + Question->Tok.setKind(tok::question); // no '?[' in clang tokens. + Question->Type = TT_CSharpNullConditionalSq; + } else { + Question->Tok.setKind(tok::question); // no '?.' in clang tokens. + Question->Type = TT_CSharpNullConditional; + } + + Tokens.erase(Tokens.end() - 1); + return true; +} + bool FormatTokenLexer::tryMergeCSharpKeywordVariables() { if (Tokens.size() < 2) return false; @@ -348,23 +374,6 @@ return true; } -// In C# merge the Identifier and the ? together e.g. arg?. -bool FormatTokenLexer::tryMergeCSharpNullConditionals() { - if (Tokens.size() < 2) - return false; - auto &Identifier = *(Tokens.end() - 2); - auto &Question = *(Tokens.end() - 1); - if (!Identifier->isOneOf(tok::r_square, tok::identifier) || - !Question->is(tok::question)) - return false; - Identifier->TokenText = - StringRef(Identifier->TokenText.begin(), - Question->TokenText.end() - Identifier->TokenText.begin()); - Identifier->ColumnWidth += Question->ColumnWidth; - Tokens.erase(Tokens.end() - 1); - return true; -} - // In C# transform identifier foreach into kw_foreach bool FormatTokenLexer::tryTransformCSharpForEach() { if (Tokens.size() < 1) Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -972,6 +972,13 @@ } break; case tok::question: + if (Tok->is(TT_CSharpNullConditionalSq)) { + if (!parseSquare()) + return false; + break; + } + if (Tok->isOneOf(TT_CSharpNullConditional, TT_CSharpNullCoalescing)) + break; if (Style.Language == FormatStyle::LK_JavaScript && Tok->Next && Tok->Next->isOneOf(tok::semi, tok::comma, tok::colon, tok::r_paren, tok::r_brace)) { @@ -987,9 +994,11 @@ if (Line.MustBeDeclaration && !Contexts.back().IsExpression && Style.Language == FormatStyle::LK_JavaScript) break; - if (Style.isCSharp() && Line.MustBeDeclaration) { - Tok->Type = TT_CSharpNullableTypeQuestionMark; - break; + if (Style.isCSharp()) { + if (Line.MustBeDeclaration && !Contexts.back().IsExpression) { + Tok->Type = TT_CSharpNullable; + break; + } } parseConditional(); break; @@ -1437,6 +1446,21 @@ // The token type is already known. return; + if (Style.isCSharp() && CurrentToken->is(tok::question)) { + if (CurrentToken->TokenText == "??") { + Current.Type = TT_CSharpNullCoalescing; + return; + } + if (CurrentToken->TokenText == "?.") { + Current.Type = TT_CSharpNullConditional; + return; + } + if (CurrentToken->TokenText == "?[") { + Current.Type = TT_CSharpNullConditionalSq; + return; + } + } + if (Style.Language == FormatStyle::LK_JavaScript) { if (Current.is(tok::exclaim)) { if (Current.Previous && @@ -2907,9 +2931,29 @@ return Style.SpacesInSquareBrackets; // No space before ? in nullable types. - if (Right.is(TT_CSharpNullableTypeQuestionMark)) + if (Right.is(TT_CSharpNullable)) + return false; + + // Require space after ? in nullable types. + if (Left.is(TT_CSharpNullable)) + return true; + + // No space before or after '?.'. + if (Left.is(TT_CSharpNullConditional) || Right.is(TT_CSharpNullConditional)) return false; + // Space before and after '??'. + if (Left.is(TT_CSharpNullCoalescing) || Right.is(TT_CSharpNullCoalescing)) + return true; + + // No space before '?['. + if (Right.is(TT_CSharpNullConditionalSq)) + return false; + + // Possible space inside `?[ 0 ]`. + if (Left.is(TT_CSharpNullConditionalSq)) + return Style.SpacesInSquareBrackets; + // space between keywords and paren e.g. "using (" if (Right.is(tok::l_paren)) if (Left.isOneOf(tok::kw_using, Keywords.kw_async, Keywords.kw_when)) Index: clang/unittests/Format/FormatTestCSharp.cpp =================================================================== --- clang/unittests/Format/FormatTestCSharp.cpp +++ clang/unittests/Format/FormatTestCSharp.cpp @@ -170,6 +170,12 @@ verifyFormat("public override string ToString() => \"{Name}\\{Age}\";"); } +TEST_F(FormatTestCSharp, CSharpConditionalExpressions) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); + // conditional expression is not seen as a NullConditional. + verifyFormat("var y = A < B ? -1 : 1;", Style); +} + TEST_F(FormatTestCSharp, CSharpNullConditional) { FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); Style.SpaceBeforeParens = FormatStyle::SBPO_Always;