diff options
author | Marek Kurdej <marek.kurdej+llvm.org@gmail.com> | 2022-02-22 22:32:03 +0100 |
---|---|---|
committer | Marek Kurdej <marek.kurdej+llvm.org@gmail.com> | 2022-02-24 10:21:02 +0100 |
commit | bfb4afee74c8d6e3b1d020564bfe163073f07a04 (patch) | |
tree | d3f5d7ca75a3b19a558a038825012953de15cf4f | |
parent | 56b5f001869664a1449e5e0fec677089153bf8c8 (diff) | |
download | llvm-bfb4afee74c8d6e3b1d020564bfe163073f07a04.zip llvm-bfb4afee74c8d6e3b1d020564bfe163073f07a04.tar.gz llvm-bfb4afee74c8d6e3b1d020564bfe163073f07a04.tar.bz2 |
[clang-format] Avoid inserting space after C++ casts.
Fixes https://github.com/llvm/llvm-project/issues/53876.
This is a solution for standard C++ casts: const_cast, dynamic_cast, reinterpret_cast, static_cast.
A general approach handling all possible casts is not possible without semantic information.
Consider the code:
```
static_cast<T>(*function_pointer_variable)(arguments);
```
vs.
```
some_return_type<T> (*function_pointer_variable)(parameters);
// Later used as:
function_pointer_variable = &some_function;
return function_pointer_variable(args);
```
In the latter case, it's not a cast but a variable declaration of a pointer to function.
Without knowing what `some_return_type<T>` is (and clang-format does not know it), it's hard to distinguish between the two cases. Theoretically, one could check whether "parameters" are types (not a cast) and "arguments" are value/expressions (a cast), but that might be inefficient (needs lots of lookahead).
Reviewed By: MyDeveloperDay, HazardyKnusperkeks, owenpan
Differential Revision: https://reviews.llvm.org/D120140
-rw-r--r-- | clang/lib/Format/FormatToken.h | 1 | ||||
-rw-r--r-- | clang/lib/Format/TokenAnnotator.cpp | 25 | ||||
-rw-r--r-- | clang/unittests/Format/FormatTest.cpp | 13 |
3 files changed, 38 insertions, 1 deletions
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index bd1d447..d1dc3ff 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -43,6 +43,7 @@ namespace format { TYPE(ConflictAlternative) \ TYPE(ConflictEnd) \ TYPE(ConflictStart) \ + TYPE(CppCastLParen) \ TYPE(CtorInitializerColon) \ TYPE(CtorInitializerComma) \ TYPE(DesignatedInitializerLSquare) \ diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index 01c215f..36ff757 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -358,7 +358,8 @@ private: if (CurrentToken->Previous->is(TT_BinaryOperator)) Contexts.back().IsExpression = true; if (CurrentToken->is(tok::r_paren)) { - if (MightBeFunctionType && ProbablyFunctionType && CurrentToken->Next && + if (Left->isNot(TT_CppCastLParen) && MightBeFunctionType && + ProbablyFunctionType && CurrentToken->Next && (CurrentToken->Next->is(tok::l_paren) || (CurrentToken->Next->is(tok::l_square) && Line.MustBeDeclaration))) Left->setType(Left->Next->is(tok::caret) ? TT_ObjCBlockLParen @@ -1733,6 +1734,9 @@ private: Current.Tok.setKind(tok::unknown); else Current.setType(TT_LineComment); + } else if (Current.is(tok::l_paren)) { + if (lParenStartsCppCast(Current)) + Current.setType(TT_CppCastLParen); } else if (Current.is(tok::r_paren)) { if (rParenEndsCast(Current)) Current.setType(TT_CastRParen); @@ -1880,6 +1884,25 @@ private: return Style.isJavaScript() && PreviousNotConst->is(tok::kw_const); } + /// Determine whether '(' is starting a C++ cast. + bool lParenStartsCppCast(const FormatToken &Tok) { + // C-style casts are only used in C++. + if (!Style.isCpp()) + return false; + + FormatToken *LeftOfParens = Tok.getPreviousNonComment(); + if (LeftOfParens && LeftOfParens->is(TT_TemplateCloser) && + LeftOfParens->MatchingParen) { + auto *Prev = LeftOfParens->MatchingParen->getPreviousNonComment(); + if (Prev && Prev->isOneOf(tok::kw_const_cast, tok::kw_dynamic_cast, + tok::kw_reinterpret_cast, tok::kw_static_cast)) + // FIXME: Maybe we should handle identifiers ending with "_cast", + // e.g. any_cast? + return true; + } + return false; + } + /// Determine whether ')' is ending a cast. bool rParenEndsCast(const FormatToken &Tok) { // C-style casts are only used in C++, C# and Java. diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 6cd6c4a..624f3a7 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -10570,6 +10570,19 @@ TEST_F(FormatTest, FormatsBinaryOperatorsPrecedingEquals) { TEST_F(FormatTest, FormatsCasts) { verifyFormat("Type *A = static_cast<Type *>(P);"); + verifyFormat("static_cast<Type *>(P);"); + verifyFormat("static_cast<Type &>(Fun)(Args);"); + verifyFormat("static_cast<Type &>(*Fun)(Args);"); + verifyFormat("if (static_cast<int>(A) + B >= 0)\n ;"); + // Check that static_cast<...>(...) does not require the next token to be on + // the same line. + verifyFormat("some_loooong_output << something_something__ << " + "static_cast<const void *>(R)\n" + " << something;"); + verifyFormat("a = static_cast<Type &>(*Fun)(Args);"); + verifyFormat("const_cast<Type &>(*Fun)(Args);"); + verifyFormat("dynamic_cast<Type &>(*Fun)(Args);"); + verifyFormat("reinterpret_cast<Type &>(*Fun)(Args);"); verifyFormat("Type *A = (Type *)P;"); verifyFormat("Type *A = (vector<Type *, int *>)P;"); verifyFormat("int a = (int)(2.0f);"); |