diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 0edf7af72c24e..511a967f66d10 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3946,6 +3946,21 @@ the configuration (without a prefix: ``Auto``). This is an experimental flag, that might go away or be renamed. Do not use this in config files, etc. Use at your own risk. +.. _ExportBlockIndentation: + +**ExportBlockIndentation** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ ` + If ``true``, clang-format will indent the body of an ``export { ... }`` + block. This doesn't affect the formatting of anything else related to + exported declarations. + + .. code-block:: c++ + + true: false: + export { vs. export { + void foo(); void foo(); + void bar(); void bar(); + } } + .. _FixNamespaceComments: **FixNamespaceComments** (``Boolean``) :versionbadge:`clang-format 5` :ref:`¶ ` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index aa1c02d04f7ca..14a3b5db05ff3 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -1224,6 +1224,7 @@ clang-format - Adds ``VariableTemplates`` option. - Adds support for bash globstar in ``.clang-format-ignore``. - Adds ``WrapNamespaceBodyWithEmptyLines`` option. +- Adds the ``ExportBlockIndentation`` option. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 7c2afd4d94ab0..c31423841ec1a 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2676,6 +2676,19 @@ struct FormatStyle { /// \version 3.7 bool ExperimentalAutoDetectBinPacking; + /// If ``true``, clang-format will indent the body of an ``export { ... }`` + /// block. This doesn't affect the formatting of anything else related to + /// exported declarations. + /// \code + /// true: false: + /// export { vs. export { + /// void foo(); void foo(); + /// void bar(); void bar(); + /// } } + /// \endcode + /// \version 20 + bool ExportBlockIndentation; + /// If ``true``, clang-format adds missing namespace end comments for /// namespaces and fixes invalid existing ones. This doesn't affect short /// namespaces, which are controlled by ``ShortNamespaceLines``. @@ -5254,6 +5267,7 @@ struct FormatStyle { EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier && ExperimentalAutoDetectBinPacking == R.ExperimentalAutoDetectBinPacking && + ExportBlockIndentation == R.ExportBlockIndentation && FixNamespaceComments == R.FixNamespaceComments && ForEachMacros == R.ForEachMacros && IncludeStyle.IncludeBlocks == R.IncludeStyle.IncludeBlocks && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index fc60c5ec5eebc..6826fa76662cf 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1040,6 +1040,7 @@ template <> struct MappingTraits { Style.EmptyLineBeforeAccessModifier); IO.mapOptional("ExperimentalAutoDetectBinPacking", Style.ExperimentalAutoDetectBinPacking); + IO.mapOptional("ExportBlockIndentation", Style.ExportBlockIndentation); IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments); IO.mapOptional("ForEachMacros", Style.ForEachMacros); IO.mapOptional("IfMacros", Style.IfMacros); @@ -1550,6 +1551,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never; LLVMStyle.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_LogicalBlock; LLVMStyle.ExperimentalAutoDetectBinPacking = false; + LLVMStyle.ExportBlockIndentation = true; LLVMStyle.FixNamespaceComments = true; LLVMStyle.ForEachMacros.push_back("foreach"); LLVMStyle.ForEachMacros.push_back("Q_FOREACH"); diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h index 16e920e8ad8a2..6aea310a56d69 100644 --- a/clang/lib/Format/TokenAnnotator.h +++ b/clang/lib/Format/TokenAnnotator.h @@ -154,6 +154,11 @@ class AnnotatedLine { startsWith(tok::kw_export, tok::kw_namespace); } + /// \c true if this line starts a C++ export block. + bool startsWithExportBlock() const { + return startsWith(tok::kw_export, tok::l_brace); + } + FormatToken *getFirstNonComment() const { assert(First); return First->is(tok::comment) ? First->getNextNonComment() : First; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index cee84fb1191ab..46545aa1f4c07 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -432,8 +432,9 @@ class LineJoiner { // Try to merge a control statement block with left brace unwrapped. if (TheLine->Last->is(tok::l_brace) && FirstNonComment != TheLine->Last && - FirstNonComment->isOneOf(tok::kw_if, tok::kw_while, tok::kw_for, - TT_ForEachMacro)) { + (FirstNonComment->isOneOf(tok::kw_if, tok::kw_while, tok::kw_for, + TT_ForEachMacro) || + TheLine->startsWithExportBlock())) { return Style.AllowShortBlocksOnASingleLine != FormatStyle::SBS_Never ? tryMergeSimpleBlock(I, E, Limit) : 0; @@ -832,7 +833,8 @@ class LineJoiner { if (IsCtrlStmt(Line) || Line.First->isOneOf(tok::kw_try, tok::kw___try, tok::kw_catch, tok::kw___finally, tok::r_brace, - Keywords.kw___except)) { + Keywords.kw___except) || + Line.startsWithExportBlock()) { if (IsSplitBlock) return 0; // Don't merge when we can't except the case when diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 198c05fd9dcd8..834693e2ecf0c 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -1625,6 +1625,10 @@ void UnwrappedLineParser::parseStructuralElement( parseNamespace(); return; } + if (FormatTok->is(tok::l_brace)) { + parseCppExportBlock(); + return; + } if (FormatTok->is(Keywords.kw_import) && parseModuleImport()) return; } @@ -3105,6 +3109,26 @@ void UnwrappedLineParser::parseTryCatch() { addUnwrappedLine(); } +void UnwrappedLineParser::parseNamespaceOrExportBlock(unsigned AddLevels) { + bool ManageWhitesmithsBraces = + AddLevels == 0u && Style.BreakBeforeBraces == FormatStyle::BS_Whitesmiths; + + // If we're in Whitesmiths mode, indent the brace if we're not indenting + // the whole block. + if (ManageWhitesmithsBraces) + ++Line->Level; + + // Munch the semicolon after the block. This is more common than one would + // think. Putting the semicolon into its own line is very ugly. + parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/true, + /*KeepBraces=*/true, /*IfKind=*/nullptr, ManageWhitesmithsBraces); + + addUnwrappedLine(AddLevels > 0 ? LineLevel::Remove : LineLevel::Keep); + + if (ManageWhitesmithsBraces) + --Line->Level; +} + void UnwrappedLineParser::parseNamespace() { assert(FormatTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) && "'namespace' expected"); @@ -3137,29 +3161,16 @@ void UnwrappedLineParser::parseNamespace() { DeclarationScopeStack.size() > 1) ? 1u : 0u; - bool ManageWhitesmithsBraces = - AddLevels == 0u && - Style.BreakBeforeBraces == FormatStyle::BS_Whitesmiths; - - // If we're in Whitesmiths mode, indent the brace if we're not indenting - // the whole block. - if (ManageWhitesmithsBraces) - ++Line->Level; - - // Munch the semicolon after a namespace. This is more common than one would - // think. Putting the semicolon into its own line is very ugly. - parseBlock(/*MustBeDeclaration=*/true, AddLevels, /*MunchSemi=*/true, - /*KeepBraces=*/true, /*IfKind=*/nullptr, - ManageWhitesmithsBraces); - - addUnwrappedLine(AddLevels > 0 ? LineLevel::Remove : LineLevel::Keep); - - if (ManageWhitesmithsBraces) - --Line->Level; + parseNamespaceOrExportBlock(AddLevels); } // FIXME: Add error handling. } +void UnwrappedLineParser::parseCppExportBlock() { + parseNamespaceOrExportBlock(/*AddLevels=*/Style.ExportBlockIndentation ? 1 + : 0); +} + void UnwrappedLineParser::parseNew() { assert(FormatTok->is(tok::kw_new) && "'new' expected"); nextToken(); diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h index 8160d5e84186e..08bff2748eb8f 100644 --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -171,6 +171,8 @@ class UnwrappedLineParser { void parseRequiresClause(FormatToken *RequiresToken); void parseRequiresExpression(FormatToken *RequiresToken); void parseConstraintExpression(); + void parseCppExportBlock(); + void parseNamespaceOrExportBlock(unsigned AddLevels); void parseJavaEnumBody(); // Parses a record (aka class) as a top level element. If ParseAsExpr is true, // parses the record as a child block, i.e. if the class declaration is an diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index f8d13cd0ce250..9623187073a0b 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -9059,6 +9059,121 @@ TEST_F(FormatTest, AdaptiveOnePerLineFormatting) { Style); } +TEST_F(FormatTest, ExportBlockIndentation) { + FormatStyle Style = getLLVMStyleWithColumns(80); + Style.ExportBlockIndentation = true; + verifyFormat("export {\n" + " int x;\n" + " int y;\n" + "}", + "export {\n" + "int x;\n" + "int y;\n" + "}", + Style); + + Style.ExportBlockIndentation = false; + verifyFormat("export {\n" + "int x;\n" + "int y;\n" + "}", + "export {\n" + " int x;\n" + " int y;\n" + "}", + Style); +} + +TEST_F(FormatTest, ShortExportBlocks) { + FormatStyle Style = getLLVMStyleWithColumns(80); + Style.ExportBlockIndentation = false; + + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Never; + verifyFormat("export {\n" + "}", + Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + "export\n" + "{\n" + "int x;\n" + "}", + Style); + + verifyFormat("export {\n" + "}", + "export {}", Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + "export { int x; }", Style); + + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Always; + verifyFormat("export {}", + "export {\n" + "}", + Style); + + verifyFormat("export { int x; }", + "export {\n" + "int x;\n" + "}", + Style); + + verifyFormat("export { int x; }", + "export\n" + "{\n" + "int x;\n" + "}", + Style); + + verifyFormat("export {}", + "export {\n" + "}", + Style); + + verifyFormat("export { int x; }", + "export {\n" + "int x;\n" + "}", + Style); + + Style.AllowShortBlocksOnASingleLine = FormatStyle::SBS_Empty; + verifyFormat("export {}", + "export {\n" + "}", + Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + "export\n" + "{\n" + "int x;\n" + "}", + Style); + + verifyFormat("export {}", Style); + + verifyFormat("export {\n" + "int x;\n" + "}", + "export { int x; }", Style); +} + TEST_F(FormatTest, FormatsBuilderPattern) { verifyFormat("return llvm::StringSwitch(name)\n" " .StartsWith(\".eh_frame_hdr\", ORDER_EH_FRAMEHDR)\n"