diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2756,6 +2756,39 @@ LoooooooooooooooooooooooooooooooooooooooongReturnType LoooooooooooooooooooooooooooooooongFunctionDeclaration(); +**InsertBraces** (``Boolean``) :versionbadge:`clang-format 15` + Insert braces after control statements (``if``, ``else``, ``for``, ``do``, + and ``while``) in C++ unless the control statements are inside macro + definitions or the braces would enclose preprocessor directives. + + .. warning:: + + Setting this option to `true` 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. + + .. code-block:: c++ + + false: true: + + if (isa(D)) vs. if (isa(D)) { + handleFunctionDecl(D); handleFunctionDecl(D); + else if (isa(D)) } else if (isa(D)) { + handleVarDecl(D); handleVarDecl(D); + else } else { + return; return; + } + + while (i--) vs. while (i--) { + for (auto *A : D.attrs()) for (auto *A : D.attrs()) { + handleAttr(A); handleAttr(A); + } + } + + do vs. do { + --i; --i; + while (i); } while (i); + **InsertTrailingCommas** (``TrailingCommaStyle``) :versionbadge:`clang-format 12` If set to ``TCS_Wrapped`` will insert trailing commas in container literals (arrays and objects) that wrap across multiple lines. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -188,6 +188,9 @@ - Changed ``BreakBeforeConceptDeclarations`` from ``Boolean`` to an enum. +- Option ``InsertBraces`` has been added to insert optional braces after control + statements. + libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2571,6 +2571,38 @@ /// \version 3.7 bool IndentWrappedFunctionNames; + /// Insert braces after control statements (``if``, ``else``, ``for``, ``do``, + /// and ``while``) in C++ unless the control statements are inside macro + /// definitions or the braces would enclose preprocessor directives. + /// \warning + /// Setting this option to `true` 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 + /// \code + /// false: true: + /// + /// if (isa(D)) vs. if (isa(D)) { + /// handleFunctionDecl(D); handleFunctionDecl(D); + /// else if (isa(D)) } else if (isa(D)) { + /// handleVarDecl(D); handleVarDecl(D); + /// else } else { + /// return; return; + /// } + /// + /// while (i--) vs. while (i--) { + /// for (auto *A : D.attrs()) for (auto *A : D.attrs()) { + /// handleAttr(A); handleAttr(A); + /// } + /// } + /// + /// do vs. do { + /// --i; --i; + /// while (i); } while (i); + /// \endcode + /// \version 15 + bool InsertBraces; + /// A vector of prefixes ordered by the desired groups for Java imports. /// /// One group's prefix can be a subset of another - the longest prefix is diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -768,6 +768,7 @@ IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); + IO.mapOptional("InsertBraces", Style.InsertBraces); IO.mapOptional("InsertTrailingCommas", Style.InsertTrailingCommas); IO.mapOptional("JavaImportGroups", Style.JavaImportGroups); IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes); @@ -1223,6 +1224,7 @@ LLVMStyle.IndentWrappedFunctionNames = false; LLVMStyle.IndentWidth = 2; LLVMStyle.PPIndentWidth = -1; + LLVMStyle.InsertBraces = false; LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None; LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave; LLVMStyle.JavaScriptWrapImports = true; @@ -1661,7 +1663,7 @@ return ParseError::DuplicateQualifierSpecified; } - // Ensure the list has 'type' in it + // Ensure the list has 'type' in it. auto type = std::find(Style->QualifierOrder.begin(), Style->QualifierOrder.end(), "type"); if (type == Style->QualifierOrder.end()) @@ -1821,6 +1823,48 @@ } }; +class BracesInserter : public TokenAnalyzer { +public: + BracesInserter(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; + insertBraces(AnnotatedLines, Result); + return {Result, 0}; + } + +private: + void insertBraces(SmallVectorImpl &Lines, + tooling::Replacements &Result) { + const auto &SourceMgr = Env.getSourceManager(); + for (AnnotatedLine *Line : Lines) { + insertBraces(Line->Children, Result); + if (!Line->Affected) + continue; + for (FormatToken *Token = Line->First; Token && !Token->Finalized; + Token = Token->Next) { + if (Token->BraceCount == 0) + continue; + std::string Brace; + if (Token->BraceCount < 0) { + assert(Token->BraceCount == -1); + Brace = '{'; + } else { + Brace = std::string(Token->BraceCount, '}'); + } + Token->BraceCount = 0; + const auto Start = Token->Tok.getEndLoc(); + cantFail(Result.add(tooling::Replacement(SourceMgr, Start, 0, Brace))); + } + } + } +}; + class JavaScriptRequoter : public TokenAnalyzer { public: JavaScriptRequoter(const Environment &Env, const FormatStyle &Style) @@ -3133,6 +3177,11 @@ }); } + if (Style.isCpp() && Style.InsertBraces) + Passes.emplace_back([&](const Environment &Env) { + return BracesInserter(Env, Expanded).process(); + }); + if (Style.isCpp() && Style.RemoveBracesLLVM) Passes.emplace_back([&](const Environment &Env) { return BracesRemover(Env, Expanded).process(); 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 @@ -489,6 +489,12 @@ /// Is optional and can be removed. bool Optional = false; + /// Number of optional braces to be inserted after this token: + /// -1: a single left brace + /// 0: no braces + /// >0: number of right braces + int8_t BraceCount = 0; + /// If this token starts a block, this contains all the unwrapped lines /// in it. SmallVector Children; diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -119,6 +119,7 @@ void parseParens(TokenType AmpAmpTokenType = TT_Unknown); void parseSquare(bool LambdaIntroducer = false); void keepAncestorBraces(); + void parseUnbracedBody(bool CheckEOF = false); FormatToken *parseIfThenElse(IfStmtKind *IfKind, bool KeepBraces = false); void parseTryCatch(); void parseForOrWhileLoop(); 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 @@ -2300,6 +2300,53 @@ NestedTooDeep.push_back(false); } +static FormatToken *getLastNonComment(const UnwrappedLine &Line) { + for (const auto &Token : llvm::reverse(Line.Tokens)) + if (Token.Tok->isNot(tok::comment)) + return Token.Tok; + + return nullptr; +} + +void UnwrappedLineParser::parseUnbracedBody(bool CheckEOF) { + FormatToken *Tok = nullptr; + + if (Style.InsertBraces && !Line->InPPDirective && !Line->Tokens.empty() && + PreprocessorDirectives.empty()) { + Tok = getLastNonComment(*Line); + assert(Tok); + if (Tok->BraceCount < 0) { + assert(Tok->BraceCount == -1); + Tok = nullptr; + } else { + Tok->BraceCount = -1; + } + } + + addUnwrappedLine(); + ++Line->Level; + parseStructuralElement(); + + if (Tok) { + assert(!Line->InPPDirective); + Tok = nullptr; + for (const auto &L : llvm::reverse(*CurrentLines)) { + if (!L.InPPDirective) { + Tok = getLastNonComment(L); + if (Tok) + break; + } + } + assert(Tok); + ++Tok->BraceCount; + } + + if (CheckEOF && FormatTok->is(tok::eof)) + addUnwrappedLine(); + + --Line->Level; +} + static void markOptionalBraces(FormatToken *LeftBrace) { if (!LeftBrace) return; @@ -2354,10 +2401,7 @@ else NeedsUnwrappedLine = true; } else { - addUnwrappedLine(); - ++Line->Level; - parseStructuralElement(); - --Line->Level; + parseUnbracedBody(); } bool KeepIfBraces = false; @@ -2403,12 +2447,7 @@ if (IsPrecededByComment) --Line->Level; } else { - addUnwrappedLine(); - ++Line->Level; - parseStructuralElement(); - if (FormatTok->is(tok::eof)) - addUnwrappedLine(); - --Line->Level; + parseUnbracedBody(/*CheckEOF=*/true); } } else { if (Style.RemoveBracesLLVM) @@ -2654,10 +2693,7 @@ } addUnwrappedLine(); } else { - addUnwrappedLine(); - ++Line->Level; - parseStructuralElement(); - --Line->Level; + parseUnbracedBody(); } if (Style.RemoveBracesLLVM) @@ -2676,10 +2712,7 @@ if (Style.BraceWrapping.BeforeWhile) addUnwrappedLine(); } else { - addUnwrappedLine(); - ++Line->Level; - parseStructuralElement(); - --Line->Level; + parseUnbracedBody(); } if (Style.RemoveBracesLLVM) 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 @@ -19474,6 +19474,7 @@ CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires"); CHECK_PARSE_BOOL(IndentRequiresClause); CHECK_PARSE_BOOL(IndentWrappedFunctionNames); + CHECK_PARSE_BOOL(InsertBraces); CHECK_PARSE_BOOL(KeepEmptyLinesAtTheStartOfBlocks); CHECK_PARSE_BOOL(ObjCSpaceAfterProperty); CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList); @@ -24300,6 +24301,202 @@ verifyFormat("template struct Foo {};", Style); } +TEST_F(FormatTest, InsertBraces) { + FormatStyle Style = getLLVMStyle(); + Style.InsertBraces = true; + + verifyFormat("// clang-format off\n" + "// comment\n" + "if (a) f();\n" + "// clang-format on\n" + "if (b) {\n" + " g();\n" + "}", + "// clang-format off\n" + "// comment\n" + "if (a) f();\n" + "// clang-format on\n" + "if (b) g();", + Style); + + verifyFormat("if (a) {\n" + " switch (b) {\n" + " case 1:\n" + " c = 0;\n" + " break;\n" + " default:\n" + " c = 1;\n" + " }\n" + "}", + "if (a)\n" + " switch (b) {\n" + " case 1:\n" + " c = 0;\n" + " break;\n" + " default:\n" + " c = 1;\n" + " }", + Style); + + verifyFormat("for (auto node : nodes) {\n" + " if (node) {\n" + " break;\n" + " }\n" + "}", + "for (auto node : nodes)\n" + " if (node)\n" + " break;", + Style); + + verifyFormat("for (auto node : nodes) {\n" + " if (node)\n" + "}", + "for (auto node : nodes)\n" + " if (node)", + Style); + + verifyFormat("do {\n" + " --a;\n" + "} while (a);", + "do\n" + " --a;\n" + "while (a);", + Style); + + verifyFormat("if (i) {\n" + " ++i;\n" + "} else {\n" + " --i;\n" + "}", + "if (i)\n" + " ++i;\n" + "else {\n" + " --i;\n" + "}", + Style); + + verifyFormat("void f() {\n" + " while (j--) {\n" + " while (i) {\n" + " --i;\n" + " }\n" + " }\n" + "}", + "void f() {\n" + " while (j--)\n" + " while (i)\n" + " --i;\n" + "}", + Style); + + verifyFormat("f({\n" + " if (a) {\n" + " g();\n" + " }\n" + "});", + "f({\n" + " if (a)\n" + " g();\n" + "});", + Style); + + verifyFormat("if (a) {\n" + " f();\n" + "} else if (b) {\n" + " g();\n" + "} else {\n" + " h();\n" + "}", + "if (a)\n" + " f();\n" + "else if (b)\n" + " g();\n" + "else\n" + " h();", + Style); + + verifyFormat("if (a) {\n" + " f();\n" + "}\n" + "// comment\n" + "/* comment */", + "if (a)\n" + " f();\n" + "// comment\n" + "/* comment */", + Style); + + verifyFormat("if (a) {\n" + " // foo\n" + " // bar\n" + " f();\n" + "}", + "if (a)\n" + " // foo\n" + " // bar\n" + " f();", + Style); + + verifyFormat("if (a) { // comment\n" + " // comment\n" + " f();\n" + "}", + "if (a) // comment\n" + " // comment\n" + " f();", + Style); + + verifyFormat("if (a) {\n" + " f();\n" + "}\n" + "#undef A\n" + "#undef B", + "if (a)\n" + " f();\n" + "#undef A\n" + "#undef B", + Style); + + verifyFormat("if (a)\n" + "#ifdef A\n" + " f();\n" + "#else\n" + " g();\n" + "#endif", + Style); + + verifyFormat("#if 0\n" + "#elif 1\n" + "#endif\n" + "void f() {\n" + " if (a) {\n" + " g();\n" + " }\n" + "}", + "#if 0\n" + "#elif 1\n" + "#endif\n" + "void f() {\n" + " if (a) g();\n" + "}", + Style); + + Style.ColumnLimit = 15; + + verifyFormat("#define A \\\n" + " if (a) \\\n" + " f();", + Style); + + verifyFormat("if (a + b >\n" + " c) {\n" + " f();\n" + "}", + "if (a + b > c)\n" + " f();", + Style); +} + TEST_F(FormatTest, RemoveBraces) { FormatStyle Style = getLLVMStyle(); Style.RemoveBracesLLVM = true;