Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -4337,6 +4337,50 @@ } } +.. _RemoveParentheses: + +**RemoveParentheses** (``RemoveParenthesesStyle``) :versionbadge:`clang-format 17` :ref:`¶ ` + Remove redundant parentheses. + + .. warning:: + + Setting this option to any value other than ``None`` could lead to + incorrect code formatting due to clang-format's lack of complete semantic + information. As such, extra care should be taken to review code changes + made by this option. + + Possible values: + + * ``RPS_None`` (in configuration: ``None``) + Do not remove parentheses. + + .. code-block:: c++ + + class __declspec((dllimport)) X {}; + co_return (((0))); + return ((a + b) - ((c + d))); + + * ``RPS_DoubleParentheses`` (in configuration: ``DoubleParentheses``) + Replace double, triple, etc. parentheses with single parentheses. + + .. code-block:: c++ + + class __declspec(dllimport) X {}; + co_return (0); + return ((a + b) - (c + d)); + + * ``RPS_ReturnStatement`` (in configuration: ``ReturnStatement``) + Also remove parentheses enclosing the expression in a + ``return``/``co_return`` statement. + + .. code-block:: c++ + + class __declspec(dllimport) X {}; + co_return 0; + return (a + b) - (c + d); + + + .. _RemoveSemicolon: **RemoveSemicolon** (``Boolean``) :versionbadge:`clang-format 16` :ref:`¶ ` Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -801,6 +801,7 @@ - Add ``BracedInitializerIndentWidth`` which can be used to configure the indentation level of the contents of braced init lists. - Add ``KeepEmptyLinesAtEOF`` to keep empty lines at end of file. +- Add ``RemoveParenthese`` to remove redundant parentheses. libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -3369,6 +3369,42 @@ /// \version 14 bool RemoveBracesLLVM; + /// Types of redundant parentheses to remove. + enum RemoveParenthesesStyle : int8_t { + /// Do not remove parentheses. + /// \code + /// class __declspec((dllimport)) X {}; + /// co_return (((0))); + /// return ((a + b) - ((c + d))); + /// \endcode + RPS_None, + /// Replace double, triple, etc. parentheses with single parentheses. + /// \code + /// class __declspec(dllimport) X {}; + /// co_return (0); + /// return ((a + b) - (c + d)); + /// \endcode + RPS_DoubleParentheses, + /// Also remove parentheses enclosing the expression in a + /// ``return``/``co_return`` statement. + /// \code + /// class __declspec(dllimport) X {}; + /// co_return 0; + /// return (a + b) - (c + d); + /// \endcode + RPS_ReturnStatement, + }; + + /// Remove redundant parentheses. + /// \warning + /// Setting this option to any value other than ``None`` could lead to + /// incorrect code formatting due to clang-format's lack of complete semantic + /// information. As such, extra care should be taken to review code changes + /// made by this option. + /// \endwarning + /// \version 17 + RemoveParenthesesStyle RemoveParentheses; + /// Remove semicolons after the closing brace of a non-empty function. /// \warning /// Setting this option to `true` could lead to incorrect code formatting due @@ -4402,6 +4438,7 @@ RawStringFormats == R.RawStringFormats && ReferenceAlignment == R.ReferenceAlignment && RemoveBracesLLVM == R.RemoveBracesLLVM && + RemoveParentheses == R.RemoveParentheses && RemoveSemicolon == R.RemoveSemicolon && RequiresClausePosition == R.RequiresClausePosition && RequiresExpressionIndentation == R.RequiresExpressionIndentation && Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -502,6 +502,15 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::RemoveParenthesesStyle &Value) { + IO.enumCase(Value, "None", FormatStyle::RPS_None); + IO.enumCase(Value, "DoubleParentheses", FormatStyle::RPS_DoubleParentheses); + IO.enumCase(Value, "ReturnStatement", FormatStyle::RPS_ReturnStatement); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, @@ -989,6 +998,7 @@ IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment); IO.mapOptional("ReflowComments", Style.ReflowComments); IO.mapOptional("RemoveBracesLLVM", Style.RemoveBracesLLVM); + IO.mapOptional("RemoveParentheses", Style.RemoveParentheses); IO.mapOptional("RemoveSemicolon", Style.RemoveSemicolon); IO.mapOptional("RequiresClausePosition", Style.RequiresClausePosition); IO.mapOptional("RequiresExpressionIndentation", @@ -1429,6 +1439,7 @@ LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer; LLVMStyle.ReflowComments = true; LLVMStyle.RemoveBracesLLVM = false; + LLVMStyle.RemoveParentheses = FormatStyle::RPS_None; LLVMStyle.RemoveSemicolon = false; LLVMStyle.RequiresClausePosition = FormatStyle::RCPS_OwnLine; LLVMStyle.RequiresExpressionIndentation = FormatStyle::REI_OuterScope; @@ -1982,6 +1993,50 @@ namespace { +class ParensRemover : public TokenAnalyzer { +public: + ParensRemover(const Environment &Env, const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override { + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + tooling::Replacements Result; + removeParens(AnnotatedLines, Result); + return {Result, 0}; + } + +private: + void removeParens(SmallVectorImpl &Lines, + tooling::Replacements &Result) { + const auto &SourceMgr = Env.getSourceManager(); + for (auto *Line : Lines) { + removeParens(Line->Children, Result); + if (!Line->Affected) + continue; + for (const auto *Token = Line->First; Token && !Token->Finalized; + Token = Token->Next) { + if (!Token->Optional || !Token->isOneOf(tok::l_paren, tok::r_paren)) + continue; + auto *Next = Token->Next; + assert(Next && Next->isNot(tok::eof)); + SourceLocation Start; + if (Next->NewlinesBefore == 0) { + Start = Token->Tok.getLocation(); + Next->WhitespaceRange = Token->WhitespaceRange; + } else { + Start = Token->WhitespaceRange.getBegin(); + } + const auto &Range = + CharSourceRange::getCharRange(Start, Token->Tok.getEndLoc()); + cantFail(Result.add(tooling::Replacement(SourceMgr, Range, " "))); + } + } + } +}; + class BracesInserter : public TokenAnalyzer { public: BracesInserter(const Environment &Env, const FormatStyle &Style) @@ -3428,6 +3483,7 @@ expandPresetsSpaceBeforeParens(Expanded); Expanded.InsertBraces = false; Expanded.RemoveBracesLLVM = false; + Expanded.RemoveParentheses = FormatStyle::RPS_None; Expanded.RemoveSemicolon = false; switch (Expanded.RequiresClausePosition) { case FormatStyle::RCPS_SingleLine: @@ -3483,6 +3539,14 @@ if (Style.QualifierAlignment != FormatStyle::QAS_Leave) addQualifierAlignmentFixerPasses(Expanded, Passes); + if (Style.RemoveParentheses != FormatStyle::RPS_None) { + FormatStyle S = Expanded; + S.RemoveParentheses = Style.RemoveParentheses; + Passes.emplace_back([&, S = std::move(S)](const Environment &Env) { + return ParensRemover(Env, S).process(/*SkipAnnotation=*/true); + }); + } + if (Style.InsertBraces) { FormatStyle S = Expanded; S.InsertBraces = true; Index: clang/lib/Format/UnwrappedLineParser.cpp =================================================================== --- clang/lib/Format/UnwrappedLineParser.cpp +++ clang/lib/Format/UnwrappedLineParser.cpp @@ -2434,6 +2434,7 @@ /// double ampersands. This applies for all nested scopes as well. void UnwrappedLineParser::parseParens(TokenType AmpAmpTokenType) { assert(FormatTok->is(tok::l_paren) && "'(' expected."); + auto *LeftParen = FormatTok; nextToken(); do { switch (FormatTok->Tok.getKind()) { @@ -2443,6 +2444,23 @@ parseChildBlock(); break; case tok::r_paren: + if (Style.RemoveParentheses > FormatStyle::RPS_None) { + const auto *Prev = LeftParen->Previous; + const auto *Next = Tokens->peekNextToken(); + const bool DoubleParens = + Prev && Prev->is(tok::l_paren) && Next && Next->is(tok::r_paren); + const auto *PrevPrev = Prev ? Prev->getPreviousNonComment() : nullptr; + const bool AttributeParens = + Style.isCpp() && PrevPrev && PrevPrev->is(tok::kw___attribute); + const bool ReturnParens = + Style.RemoveParentheses == FormatStyle::RPS_ReturnStatement && + Prev && Prev->isOneOf(tok::kw_return, tok::kw_co_return) && Next && + Next->is(tok::semi); + if ((DoubleParens && !AttributeParens) || ReturnParens) { + LeftParen->Optional = true; + FormatTok->Optional = true; + } + } nextToken(); return; case tok::r_brace: Index: clang/unittests/Format/ConfigParseTest.cpp =================================================================== --- clang/unittests/Format/ConfigParseTest.cpp +++ clang/unittests/Format/ConfigParseTest.cpp @@ -917,6 +917,13 @@ LineEnding, FormatStyle::LE_CRLF); Style.LineEnding = DefaultLineEnding; CHECK_PARSE("UseCRLF: true", LineEnding, FormatStyle::LE_DeriveCRLF); + + CHECK_PARSE("RemoveParentheses: DoubleParentheses", RemoveParentheses, + FormatStyle::RPS_DoubleParentheses); + CHECK_PARSE("RemoveParentheses: ReturnStatement", RemoveParentheses, + FormatStyle::RPS_ReturnStatement); + CHECK_PARSE("RemoveParentheses: None", RemoveParentheses, + FormatStyle::RPS_None); } TEST(ConfigParseTest, ParsesConfigurationWithLanguages) { Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -25805,6 +25805,31 @@ getGoogleStyle()); } +TEST_F(FormatTest, RemoveParentheses) { + FormatStyle Style = getLLVMStyle(); + EXPECT_EQ(Style.RemoveParentheses, FormatStyle::RPS_None); + + verifyFormat("class __declspec((dllimport)) X {};", Style); + verifyFormat("return (0);", Style); + + Style.RemoveParentheses = FormatStyle::RPS_DoubleParentheses; + verifyFormat("int x __attribute__ /**/ ((aligned(16))) = 0;", Style); + verifyFormat("class __declspec(dllimport) X {};", + "class __declspec((dllimport)) X {};", Style); + verifyFormat("return (0);", "return (((0)));", Style); + + Style.RemoveParentheses = FormatStyle::RPS_ReturnStatement; + verifyFormat("return 0;", "return (0);", Style); + verifyFormat("co_return 0;", "co_return ((0));", Style); + verifyFormat("return 0;", "return (((0)));", Style); + + Style.ColumnLimit = 25; + verifyFormat("return (a + b) - (c + d);", + "return (((a + b)) -\n" + " ((c + d)));", + Style); +} + } // namespace } // namespace test } // namespace format