[clang-format] Detect pointer qualifiers in cast expressions

When guessing whether a closing paren is then end of a cast expression also
skip over pointer qualifiers while looking for TT_PointerOrReference.
This prevents some address-of and dereference operators from being parsed
as a binary operator.

Before:
x = (foo *const) * v;
x = (foo *const volatile restrict __attribute__((foo)) _Nonnull _Null_unspecified _Nonnull) & v;

After:
x = (foo *const)*v;
x = (foo *const volatile restrict __attribute__((foo)) _Nonnull _Null_unspecified _Nonnull)&v;

Reviewed By: MyDeveloperDay

Differential Revision: https://reviews.llvm.org/D86716
This commit is contained in:
Alex Richardson 2020-08-28 11:03:10 +01:00
parent d304360dec
commit 96824abe7d
3 changed files with 59 additions and 8 deletions

View File

@ -439,6 +439,12 @@ public:
(!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;

View File

@ -1827,10 +1827,30 @@ private:
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 @@ private:
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;

View File

@ -8127,6 +8127,33 @@ TEST_F(FormatTest, UnderstandsAttributes) {
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);");