diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -2602,16 +2602,17 @@ // Handle AttributeMacro, e.g. `if (x) UNLIKELY`. if (FormatTok->is(TT_AttributeMacro)) nextToken(); - handleCppAttributes(); + if (FormatTok->is(tok::l_square)) + handleCppAttributes(); } bool UnwrappedLineParser::handleCppAttributes() { // Handle [[likely]] / [[unlikely]] attributes. - if (FormatTok->is(tok::l_square) && tryToParseSimpleAttribute()) { - parseSquare(); - return true; - } - return false; + assert(FormatTok->is(tok::l_square)); + if (!tryToParseSimpleAttribute()) + return false; + parseSquare(); + return true; } /// Returns whether \c Tok begins a block. @@ -3728,13 +3729,13 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr) { const FormatToken &InitialToken = *FormatTok; nextToken(); - handleAttributes(); // The actual identifier can be a nested name specifier, and in macros // it is often token-pasted. + // An [[attribute]] can be before the identifier. while (FormatTok->isOneOf(tok::identifier, tok::coloncolon, tok::hashhash, tok::kw___attribute, tok::kw___declspec, - tok::kw_alignas) || + tok::kw_alignas, tok::l_square) || ((Style.Language == FormatStyle::LK_Java || Style.isJavaScript()) && FormatTok->isOneOf(tok::period, tok::comma))) { if (Style.isJavaScript() && @@ -3748,16 +3749,15 @@ continue; } } + if (FormatTok->is(tok::l_square) && handleCppAttributes()) + continue; bool IsNonMacroIdentifier = FormatTok->is(tok::identifier) && FormatTok->TokenText != FormatTok->TokenText.upper(); nextToken(); // We can have macros in between 'class' and the class name. - if (!IsNonMacroIdentifier) { - if (FormatTok->is(tok::l_paren)) { - parseParens(); - } - } + if (!IsNonMacroIdentifier && FormatTok->is(tok::l_paren)) + parseParens(); } // Note that parsing away template declarations here leads to incorrectly diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -25308,6 +25308,11 @@ "};", Style); + verifyFormat("struct EXPORT_MACRO [[nodiscard]] C {\n" + " int i;\n" + "};", + Style); + verifyIncompleteFormat("class C final [[deprecated(l]] {});", Style); // These tests are here to show a problem that may not be easily diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp --- a/clang/unittests/Format/TokenAnnotatorTest.cpp +++ b/clang/unittests/Format/TokenAnnotatorTest.cpp @@ -374,6 +374,14 @@ auto Tokens = annotate("struct S {};"); EXPECT_EQ(Tokens.size(), 6u) << Tokens; EXPECT_TOKEN(Tokens[2], tok::l_brace, TT_StructLBrace); + + Tokens = annotate("struct EXPORT_MACRO [[nodiscard]] C { int i; };"); + EXPECT_EQ(Tokens.size(), 15u) << Tokens; + EXPECT_TOKEN(Tokens[8], tok::l_brace, TT_StructLBrace); + + Tokens = annotate("struct [[deprecated]] [[nodiscard]] C { int i; };"); + EXPECT_EQ(Tokens.size(), 19u) << Tokens; + EXPECT_TOKEN(Tokens[12], tok::l_brace, TT_StructLBrace); } TEST_F(TokenAnnotatorTest, UnderstandsUnions) {