Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -1918,8 +1918,32 @@ return TT_BinaryOperator; } - // It is very unlikely that we are going to find a pointer or reference type - // definition on the RHS of an assignment. + // Try to distinguish (A && B) from (A &&b), which is ambiguous without + // other semantic information + // However in a noexcept context where it is going to be a boolean + // operation noexcept(A && b) resulting in a binary operation + if (Tok.is(tok::ampamp) && PrevToken && + PrevToken->isOneOf(tok::identifier, TT_TemplateCloser) && NextToken) { + const FormatToken *NextNextToken = NextToken->getNextNonComment(); + const FormatToken *PrevPrevToken = PrevToken->getPreviousNonComment(); + if (NextToken->is(tok::identifier)) { + if (NextNextToken && NextNextToken->isOneOf(tok::less, tok::coloncolon)) + return TT_BinaryOperator; + if (PrevPrevToken) { + const FormatToken *PrevPrevPrevToken = + PrevPrevToken->getPreviousNonComment(); + // We already know its `x y identifier && identifer z` + // just need to confirm x,y,z + if (PrevPrevPrevToken && PrevPrevPrevToken->is(tok::kw_noexcept) && + PrevPrevToken->is(tok::l_paren) && + NextNextToken->is(tok::r_paren)) + return TT_BinaryOperator; + } + } + } + + // It is very unlikely that we are going to find a pointer or reference + // type definition on the RHS of an assignment. if (IsExpression && !Contexts.back().CaretFound) return TT_BinaryOperator; @@ -1969,12 +1993,13 @@ bool AutoFound; const AdditionalKeywords &Keywords; - // Set of "<" tokens that do not open a template parameter list. If parseAngle - // determines that a specific token can't be a template opener, it will make - // same decision irrespective of the decisions for tokens leading up to it. - // Store this information to prevent this from causing exponential runtime. + // Set of "<" tokens that do not open a template parameter list. If + // parseAngle determines that a specific token can't be a template opener, + // it will make same decision irrespective of the decisions for tokens + // leading up to it. Store this information to prevent this from causing + // exponential runtime. llvm::SmallPtrSet NonTemplateLess; -}; +}; // namespace static const int PrecedenceUnaryOperator = prec::PointerToMember + 1; static const int PrecedenceArrowAndPeriod = prec::PointerToMember + 2; @@ -1999,7 +2024,8 @@ if (!Current || Precedence > PrecedenceArrowAndPeriod) return; - // Conditional expressions need to be parsed separately for proper nesting. + // Conditional expressions need to be parsed separately for proper + // nesting. if (Precedence == prec::Conditional) { parseConditionalExpr(); return; @@ -2042,8 +2068,8 @@ // Consume scopes: (), [], <> and {} if (Current->opensScope()) { - // In fragment of a JavaScript template string can look like '}..${' and - // thus close a scope and open a new one at the same time. + // In fragment of a JavaScript template string can look like '}..${' + // and thus close a scope and open a new one at the same time. while (Current && (!Current->closesScope() || Current->opensScope())) { next(); parse(); @@ -2178,7 +2204,7 @@ FormatToken *Current; }; -} // end anonymous namespace +} // namespace void TokenAnnotator::setCommentLineLevels( SmallVectorImpl &Lines) { @@ -2896,6 +2922,11 @@ // No whitespace in x(/*foo=*/1), except for JavaScript. return Style.Language == FormatStyle::LK_JavaScript || !Left.TokenText.endswith("=*/"); + + // Space between template and attribute. + // e.g. template [[nodiscard]] ... + if (Left.is(TT_TemplateCloser) && Right.is(TT_AttributeSquare)) + return true; if (Right.is(tok::l_paren)) { if ((Left.is(tok::r_paren) && Left.is(TT_AttributeParen)) || (Left.is(tok::r_square) && Left.is(TT_AttributeSquare))) Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -8005,6 +8005,8 @@ verifyFormat("@[ [NSArray class] ];"); verifyFormat("@[ [foo enum] ];"); + verifyFormat("template [[nodiscard]] int a() { return 1; }"); + // Make sure we do not parse attributes as lambda introducers. FormatStyle MultiLineFunctions = getLLVMStyle(); MultiLineFunctions.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; @@ -8091,6 +8093,37 @@ verifyFormat("#define A(a, b) (a && b)"); } +TEST_F(FormatTest, BinaryOperationsInANoExceptContext) { + verifyFormat("int f(int &&a) {}"); + verifyFormat("template void swap() noexcept(Bar && Foo);"); + verifyFormat( + "template void swap() noexcept(Bar::value && Foo);"); + verifyFormat("template void swap() noexcept(Bar::value && " + "Foo::value);"); + verifyFormat("template void foo(Bar::value &&b);"); + verifyFormat("template void swap() noexcept(Bar::value && " + "std::Foo);"); + verifyFormat( + "template void foo(Bar::value &&a, Foo &&b);"); + verifyFormat("template void swap() noexcept(Bar &&b);"); + verifyFormat( + "template void swap() noexcept(Bar && std::Foo);"); + verifyFormat("template void foo(Bar &&a, Foo &&b);"); + verifyFormat("template void swap() noexcept(Bar && b);"); + verifyFormat( + "template void swap() noexcept(Bar && std::Foo);"); + verifyFormat("template void foo(Bar &&a, Foo &&b);"); + verifyFormat( + "template void swap() noexcept(Bar() && Foo());"); + + verifyFormat("template void swap() noexcept(a && b);"); + verifyFormat("template void swap() noexcept(/*A*/ a && b);"); + verifyFormat("template void swap() noexcept(a && b /*A*/);"); + verifyFormat( + "template void swap() noexcept(/*A*/ a && /*A*/ b);"); + verifyFormat("template void foo(Bar &&b);"); +} + TEST_F(FormatTest, FormatsBinaryOperatorsPrecedingEquals) { verifyFormat("void f() {\n" " x[aaaaaaaaa -\n"