diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -439,6 +439,12 @@ (!ColonRequired || (Next && Next->is(tok::colon))); } + bool canBePointerOrReferenceQualifier() const { + return isOneOf(tok::kw_const, tok::kw_restrict, tok::kw_volatile, + tok::kw___attribute, tok::kw__Nonnull, tok::kw__Nullable, + tok::kw__Null_unspecified); + } + /// Determine whether the token is a simple-type-specifier. bool isSimpleTypeSpecifier() const; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1827,10 +1827,30 @@ return true; // Heuristically try to determine whether the parentheses contain a type. - bool ParensAreType = - !Tok.Previous || - Tok.Previous->isOneOf(TT_PointerOrReference, TT_TemplateCloser) || - Tok.Previous->isSimpleTypeSpecifier(); + auto IsQualifiedPointerOrReference = [](FormatToken *T) { + // This is used to handle cases such as x = (foo *const)&y; + assert(!T->isSimpleTypeSpecifier() && "Should have already been checked"); + // Strip trailing qualifiers such as const or volatile when checking + // whether the parens could be a cast to a pointer/reference type. + while (T) { + if (T->is(TT_AttributeParen)) { + // Handle `x = (foo *__attribute__((foo)))&v;`: + if (T->MatchingParen && T->MatchingParen->Previous && + T->MatchingParen->Previous->is(tok::kw___attribute)) { + T = T->MatchingParen->Previous->Previous; + continue; + } + } else if (T->canBePointerOrReferenceQualifier()) { + T = T->Previous; + continue; + } + break; + } + return T && T->is(TT_PointerOrReference); + }; + bool ParensAreType = !Tok.Previous || Tok.Previous->is(TT_TemplateCloser) || + Tok.Previous->isSimpleTypeSpecifier() || + IsQualifiedPointerOrReference(Tok.Previous); bool ParensCouldEndDecl = Tok.Next->isOneOf(tok::equal, tok::semi, tok::l_brace, tok::greater); if (ParensAreType && !ParensCouldEndDecl) @@ -1890,10 +1910,8 @@ const FormatToken *NextToken = Tok.getNextNonComment(); if (!NextToken || - NextToken->isOneOf( - tok::arrow, tok::equal, tok::kw_const, tok::kw_restrict, - tok::kw_volatile, tok::kw___attribute, tok::kw__Nonnull, - tok::kw__Nullable, tok::kw__Null_unspecified, tok::kw_noexcept) || + NextToken->isOneOf(tok::arrow, tok::equal, tok::kw_noexcept) || + NextToken->canBePointerOrReferenceQualifier() || (NextToken->is(tok::l_brace) && !NextToken->getNextNonComment())) return TT_PointerOrReference; 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 @@ -8127,6 +8127,33 @@ AfterType); } +TEST_F(FormatTest, UnderstandsPointerQualifiersInCast) { + // Check that qualifiers on pointers don't break parsing of casts. + verifyFormat("x = (foo *const)*v;"); + verifyFormat("x = (foo *volatile)*v;"); + verifyFormat("x = (foo *restrict)*v;"); + verifyFormat("x = (foo *__attribute__((foo)))*v;"); + verifyFormat("x = (foo *_Nonnull)*v;"); + verifyFormat("x = (foo *_Nullable)*v;"); + verifyFormat("x = (foo *_Null_unspecified)*v;"); + verifyFormat("x = (foo *_Nonnull)*v;"); + + // Check that we handle multiple trailing qualifiers and skip them all to + // determine that the expression is a cast to a pointer type. + FormatStyle LongPointerRight = getLLVMStyleWithColumns(999); + FormatStyle LongPointerLeft = getLLVMStyleWithColumns(999); + LongPointerLeft.PointerAlignment = FormatStyle::PAS_Left; + StringRef AllQualifiers = "const volatile restrict __attribute__((foo)) " + "_Nonnull _Null_unspecified _Nonnull"; + verifyFormat(("x = (foo *" + AllQualifiers + ")*v;").str(), LongPointerRight); + verifyFormat(("x = (foo* " + AllQualifiers + ")*v;").str(), LongPointerLeft); + + // Also check that address-of is not parsed as a binary bitwise-and: + verifyFormat("x = (foo *const)&v;"); + verifyFormat(("x = (foo *" + AllQualifiers + ")&v;").str(), LongPointerRight); + verifyFormat(("x = (foo* " + AllQualifiers + ")&v;").str(), LongPointerLeft); +} + TEST_F(FormatTest, UnderstandsSquareAttributes) { verifyFormat("SomeType s [[unused]] (InitValue);"); verifyFormat("SomeType s [[gnu::unused]] (InitValue);");