Author: Dmitrii Lebed Date: 2026-05-14T10:03:10+02:00 New Revision: f098a2272403ac08ffa630cc9b62f7f37c6191d7
URL: https://github.com/llvm/llvm-project/commit/f098a2272403ac08ffa630cc9b62f7f37c6191d7 DIFF: https://github.com/llvm/llvm-project/commit/f098a2272403ac08ffa630cc9b62f7f37c6191d7.diff LOG: [clang-format] Add BreakBeforeReturnType option (#197268) In certain codebases (e.g. embedded) — function declarations could accumulate a long prefix of specifiers and attributes (`static`, `inline`, `__attribute__((...))`, project-specific `AttributeMacros`, etc.) before the return type, which buries the core prototype and pushes parameters past the column limit. This patch adds a `BreakBeforeReturnType` style option that places that prefix on its own line(s): ```cpp __attribute__((always_inline)) static inline int do_thing(int a, int b, int c); ``` The recognized prefix tokens are function/storage specifiers (`static`, `extern`, `inline`, `virtual`, `constexpr`, `consteval`, `friend`, `export`, `_Noreturn`, `__forceinline`), C++11 attribute groups `[[...]]`, GNU/MSVC attribute groups `__attribute__((...))` / `__declspec(...)`, and identifiers configured via `AttributeMacros`. The new `BreakBeforeReturnTypeStyle` enum has values `None`, `All`, `TopLevel`, `AllDefinitions`, and `TopLevelDefinitions`. The default is `None`, preserving previous behavior. Constructors and destructors are not affected. The option composes with `BreakAfterReturnType`, `BreakAfterAttributes`, and `BreakTemplateDeclarations`. `ContinuationIndenter::getNewLineColumn` is adjusted so the wrapped return type is dedented to the line's base indent when the preceding token is a function/storage specifier keyword, matching the behavior already used after attribute groups. Adds tests in `FormatTest.cpp`. Assisted-by: Claude (claude-opus-4-7, Claude Code) Added: Modified: clang/docs/ClangFormatStyleOptions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Format/Format.h clang/lib/Format/ContinuationIndenter.cpp clang/lib/Format/Format.cpp clang/lib/Format/FormatToken.h clang/lib/Format/TokenAnnotator.cpp clang/lib/Format/TokenAnnotator.h clang/unittests/Format/ConfigParseTest.cpp clang/unittests/Format/FormatTest.cpp Removed: ################################################################################ diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index f852f76f5038c..7b1b7a7384b07 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3694,6 +3694,38 @@ the configuration (without a prefix: ``Auto``). +.. _BreakBeforeReturnType: + +**BreakBeforeReturnType** (``BreakBeforeReturnTypeStyle``) :versionbadge:`clang-format 23` :ref:`¶ <BreakBeforeReturnType>` + The function declaration/definition return type breaking style to use. + Trailing return types (``auto f() -> T``) are not affected. To have + identifier macros (e.g. ``__always_inline``) treated as specifiers, + add them to ``AttributeMacros``. + + Possible values: + + * ``BBRTS_None`` (in configuration: ``None``) + Do not force a break before the return type. + + * ``BBRTS_All`` (in configuration: ``All``) + Always break before the return type. + + .. code-block:: c++ + + static inline + void f(); + + * ``BBRTS_TopLevel`` (in configuration: ``TopLevel``) + Break before the return type of top-level functions only. + + * ``BBRTS_AllDefinitions`` (in configuration: ``AllDefinitions``) + Break before the return type of function definitions only. + + * ``BBRTS_TopLevelDefinitions`` (in configuration: ``TopLevelDefinitions``) + Break before the return type of top-level definitions only. + + + .. _BreakBeforeTemplateCloser: **BreakBeforeTemplateCloser** (``Boolean``) :versionbadge:`clang-format 21` :ref:`¶ <BreakBeforeTemplateCloser>` diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b49286b35c6b0..a9884beee2710 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -769,6 +769,8 @@ clang-format declaration parameters. - Add ``EnumAssignments`` option to ``AlignConsecutiveAssignments`` for aligning enum assignments without affecting other assignments. +- Add ``BreakBeforeReturnType`` option to break before the function return + type. libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index eca3cc44c41b6..27b2d8f4a405b 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2462,6 +2462,31 @@ struct FormatStyle { /// \version 16 BreakBeforeInlineASMColonStyle BreakBeforeInlineASMColon; + /// Different ways to break before the function return type. + enum BreakBeforeReturnTypeStyle : int8_t { + /// Do not force a break before the return type. + BBRTS_None, + /// Always break before the return type. + /// \code + /// static inline + /// void f(); + /// \endcode + BBRTS_All, + /// Break before the return type of top-level functions only. + BBRTS_TopLevel, + /// Break before the return type of function definitions only. + BBRTS_AllDefinitions, + /// Break before the return type of top-level definitions only. + BBRTS_TopLevelDefinitions, + }; + + /// The function declaration/definition return type breaking style to use. + /// Trailing return types (``auto f() -> T``) are not affected. To have + /// identifier macros (e.g. ``__always_inline``) treated as specifiers, + /// add them to ``AttributeMacros``. + /// \version 23 + BreakBeforeReturnTypeStyle BreakBeforeReturnType; + /// If ``true``, break before a template closing bracket (``>``) when there is /// a line break after the matching opening bracket (``<``). /// \code @@ -6092,6 +6117,7 @@ struct FormatStyle { BreakBeforeCloseBracketSwitch == R.BreakBeforeCloseBracketSwitch && BreakBeforeConceptDeclarations == R.BreakBeforeConceptDeclarations && BreakBeforeInlineASMColon == R.BreakBeforeInlineASMColon && + BreakBeforeReturnType == R.BreakBeforeReturnType && BreakBeforeTemplateCloser == R.BreakBeforeTemplateCloser && BreakBeforeTernaryOperators == R.BreakBeforeTernaryOperators && BreakBinaryOperations == R.BreakBinaryOperations && diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp index 338515ec6da21..361072127f8e1 100644 --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -1654,7 +1654,9 @@ ContinuationIndenter::getNewLineColumn(const LineState &State) { TT_JavaAnnotation, TT_LeadingJavaAnnotation))) || (!Style.IndentWrappedFunctionNames && - NextNonComment->isOneOf(tok::kw_operator, TT_FunctionDeclarationName))) { + NextNonComment->isOneOf(tok::kw_operator, TT_FunctionDeclarationName)) || + (State.Line->ReturnTypeWrapped && PreviousNonComment && + isReturnTypePrefixSpecifier(*PreviousNonComment))) { return std::max(IndentationAndAlignment(CurrentState.LastSpace), CurrentState.Indent); } diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index ec0ad98f37753..a29d62c99bb95 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -749,6 +749,19 @@ struct ScalarEnumerationTraits<FormatStyle::ReturnTypeBreakingStyle> { } }; +template <> +struct ScalarEnumerationTraits<FormatStyle::BreakBeforeReturnTypeStyle> { + static void enumeration(IO &IO, + FormatStyle::BreakBeforeReturnTypeStyle &Value) { + IO.enumCase(Value, "None", FormatStyle::BBRTS_None); + IO.enumCase(Value, "All", FormatStyle::BBRTS_All); + IO.enumCase(Value, "TopLevel", FormatStyle::BBRTS_TopLevel); + IO.enumCase(Value, "AllDefinitions", FormatStyle::BBRTS_AllDefinitions); + IO.enumCase(Value, "TopLevelDefinitions", + FormatStyle::BBRTS_TopLevelDefinitions); + } +}; + template <> struct ScalarEnumerationTraits<FormatStyle::SeparateDefinitionStyle> { static void enumeration(IO &IO, FormatStyle::SeparateDefinitionStyle &Value) { @@ -1317,6 +1330,7 @@ template <> struct MappingTraits<FormatStyle> { IO.mapOptional("BreakBeforeBraces", Style.BreakBeforeBraces); IO.mapOptional("BreakBeforeInlineASMColon", Style.BreakBeforeInlineASMColon); + IO.mapOptional("BreakBeforeReturnType", Style.BreakBeforeReturnType); IO.mapOptional("BreakBeforeTemplateCloser", Style.BreakBeforeTemplateCloser); IO.mapOptional("BreakBeforeTernaryOperators", @@ -1889,6 +1903,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.BreakBeforeCloseBracketSwitch = false; LLVMStyle.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Always; LLVMStyle.BreakBeforeInlineASMColon = FormatStyle::BBIAS_OnlyMultiline; + LLVMStyle.BreakBeforeReturnType = FormatStyle::BBRTS_None; LLVMStyle.BreakBeforeTemplateCloser = false; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBinaryOperations = {FormatStyle::BBO_Never, {}}; diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index 1d8f0f1cfe412..7f6721a87877a 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -2132,6 +2132,15 @@ inline bool continuesLineComment(const FormatToken &FormatTok, // Returns \c true if \c Current starts a new parameter. bool startsNextParameter(const FormatToken &Current, const FormatStyle &Style); +// Returns \c true if \c Tok is a function/storage specifier that may appear +// before a function return type (e.g. ``static``, ``inline``, ``constexpr``). +inline bool isReturnTypePrefixSpecifier(const FormatToken &Tok) { + return Tok.isOneOf(tok::kw_static, tok::kw_extern, tok::kw_inline, + tok::kw_virtual, tok::kw_constexpr, tok::kw_consteval, + tok::kw_friend, tok::kw_export, tok::kw__Noreturn, + tok::kw___forceinline); +} + } // namespace format } // namespace clang diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index afdb59617fb2a..9181e0e9d5a2a 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -4062,6 +4062,67 @@ bool TokenAnnotator::mustBreakForReturnType(const AnnotatedLine &Line) const { return false; } +bool TokenAnnotator::mustBreakBeforeReturnType( + const AnnotatedLine &Line) const { + assert(Line.MightBeFunctionDecl); + + switch (Style.BreakBeforeReturnType) { + case FormatStyle::BBRTS_None: + return false; + case FormatStyle::BBRTS_All: + return true; + case FormatStyle::BBRTS_TopLevel: + return Line.Level == 0; + case FormatStyle::BBRTS_AllDefinitions: + return Line.mightBeFunctionDefinition(); + case FormatStyle::BBRTS_TopLevelDefinitions: + return Line.Level == 0 && Line.mightBeFunctionDefinition(); + } + + return false; +} + +static FormatToken *findReturnTypeStart(const AnnotatedLine &Line) { + auto *Tok = Line.getFirstNonComment(); + if (!Tok) + return nullptr; + + if (Tok->is(tok::kw_template)) { + auto *Opener = Tok->Next; + while (Opener && Opener->isNot(TT_TemplateOpener)) + Opener = Opener->Next; + if (!Opener || !Opener->MatchingParen) + return nullptr; + Tok = Opener->MatchingParen->Next; + } + + if (Tok && Tok->is(TT_RequiresClause)) { + while (Tok && !Tok->ClosesRequiresClause) + Tok = Tok->Next; + if (Tok) + Tok = Tok->Next; + } + + while (Tok) { + if (isReturnTypePrefixSpecifier(*Tok) || + Tok->isOneOf(tok::kw___attribute, tok::kw___declspec, + TT_AttributeMacro)) { + auto *Next = Tok->Next; + if (Next && Next->is(tok::l_paren) && Next->MatchingParen) + Tok = Next->MatchingParen->Next; + else + Tok = Next; + continue; + } + if (Tok->is(TT_AttributeLSquare) && Tok->MatchingParen) { + Tok = Tok->MatchingParen->Next; + continue; + } + break; + } + return Tok; +} + void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const { if (Line.Computed) return; @@ -4180,6 +4241,17 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) const { } } + if (Line.MightBeFunctionDecl && LineIsFunctionDeclaration && + mustBreakBeforeReturnType(Line)) { + if (auto *ReturnTypeStart = findReturnTypeStart(Line); + ReturnTypeStart && ReturnTypeStart != FirstNonComment && + ReturnTypeStart->isNoneOf(TT_FunctionDeclarationName, + TT_CtorDtorDeclName, tok::tilde)) { + ReturnTypeStart->MustBreakBefore = true; + Line.ReturnTypeWrapped = true; + } + } + if (First->is(TT_ElseLBrace)) { First->CanBreakBefore = true; First->MustBreakBefore = true; diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h index 33c7df9d0f949..52d6e5ca56915 100644 --- a/clang/lib/Format/TokenAnnotator.h +++ b/clang/lib/Format/TokenAnnotator.h @@ -256,6 +256,8 @@ class TokenAnnotator { bool mustBreakForReturnType(const AnnotatedLine &Line) const; + bool mustBreakBeforeReturnType(const AnnotatedLine &Line) const; + void printDebugInfo(const AnnotatedLine &Line) const; void calculateUnbreakableTailLengths(AnnotatedLine &Line) const; diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index f731922030777..eeaf5d3f66d96 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -927,6 +927,18 @@ TEST(ConfigParseTest, ParsesConfiguration) { CHECK_PARSE("AlwaysBreakAfterReturnType: TopLevelDefinitions", BreakAfterReturnType, FormatStyle::RTBS_TopLevelDefinitions); + Style.BreakBeforeReturnType = FormatStyle::BBRTS_All; + CHECK_PARSE("BreakBeforeReturnType: None", BreakBeforeReturnType, + FormatStyle::BBRTS_None); + CHECK_PARSE("BreakBeforeReturnType: All", BreakBeforeReturnType, + FormatStyle::BBRTS_All); + CHECK_PARSE("BreakBeforeReturnType: TopLevel", BreakBeforeReturnType, + FormatStyle::BBRTS_TopLevel); + CHECK_PARSE("BreakBeforeReturnType: AllDefinitions", BreakBeforeReturnType, + FormatStyle::BBRTS_AllDefinitions); + CHECK_PARSE("BreakBeforeReturnType: TopLevelDefinitions", + BreakBeforeReturnType, FormatStyle::BBRTS_TopLevelDefinitions); + Style.BreakTemplateDeclarations = FormatStyle::BTDS_Yes; CHECK_PARSE("BreakTemplateDeclarations: Leave", BreakTemplateDeclarations, FormatStyle::BTDS_Leave); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index 4245bd1c58153..dbc8a00ad1c9b 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -10594,6 +10594,172 @@ TEST_F(FormatTest, ReturnTypeBreakingStyle) { verifyFormat("void foo (int a, int b);", Style); } +TEST_F(FormatTest, BreakBeforeReturnType) { + FormatStyle Style = getLLVMStyle(); + Style.BreakBeforeReturnType = FormatStyle::BBRTS_All; + + verifyFormat("static inline\n" + "void myfun(void);", + Style); + verifyFormat("static\n" + "int x(void);", + Style); + + verifyFormat("void f(void);", Style); + verifyFormat("int g(int a);", Style); + + // Constructors and destructors are not affected. + verifyFormat("class C {\n" + " explicit C(int);\n" + " virtual ~C();\n" + "};", + Style); + + verifyFormat("__attribute__((always_inline)) static inline\n" + "void f(void);", + Style); + verifyFormat("static __forceinline\n" + "void f(void);", + Style); + verifyFormat("export\n" + "int f();", + Style); + verifyFormat( + "__attribute__((section(\".init\"), always_inline)) static inline\n" + "int boot(void);", + Style); + verifyFormat("[[nodiscard]] static constexpr\n" + "int f();", + Style); + verifyFormat("static\n" + "const struct foo *g(void);", + Style); + verifyFormat("class A {\n" + " friend\n" + " int f();\n" + "};", + Style); + + verifyFormat("static int x = 0;", Style); + verifyFormat("static const char *msg;", Style); + + verifyFormat("static\n" + "auto f() -> int;", + Style); + + Style.ColumnLimit = 50; + verifyFormat("__attribute__((always_inline)) static inline\n" + "int do_thing(int a, int b, int c);", + Style); + Style.ColumnLimit = 80; + + verifyFormat("static inline\n" + "int compute(int x) {\n" + " ++x;\n" + " return x;\n" + "}", + Style); + + Style.BreakAfterReturnType = FormatStyle::RTBS_All; + verifyFormat("static inline\n" + "void\n" + "f(void);", + Style); + Style.BreakAfterReturnType = FormatStyle::RTBS_None; + + Style.BreakAfterAttributes = FormatStyle::ABS_Always; + verifyFormat("[[nodiscard]]\n" + "static\n" + "int f();", + Style); + Style.BreakAfterAttributes = FormatStyle::ABS_Leave; + + Style.BreakTemplateDeclarations = FormatStyle::BTDS_Yes; + verifyFormat("template <typename T>\n" + "static inline\n" + "T f();", + Style); + verifyFormat("template <typename T>\n" + " requires Foo<T>\n" + "static inline\n" + "T f();", + Style); + Style.BreakTemplateDeclarations = FormatStyle::BTDS_Leave; + + Style.BreakBeforeReturnType = FormatStyle::BBRTS_AllDefinitions; + verifyFormat("class A {\n" + " static inline int member();\n" + " static inline\n" + " int member_def() {\n" + " return 0;\n" + " }\n" + "};\n" + "static inline int top_decl();\n" + "static inline\n" + "int top_defn() {\n" + " ++x;\n" + " return 0;\n" + "}", + Style); + + Style.BreakBeforeReturnType = FormatStyle::BBRTS_TopLevel; + verifyFormat("class A {\n" + " static inline int member();\n" + " static inline int member_def() { return 0; }\n" + "};\n" + "static inline\n" + "int top_decl();\n" + "static inline\n" + "int top_defn() {\n" + " ++x;\n" + " return 0;\n" + "}", + Style); + + Style.BreakBeforeReturnType = FormatStyle::BBRTS_TopLevelDefinitions; + verifyFormat("class A {\n" + " static inline int member();\n" + " static inline int member_def() { return 0; }\n" + "};\n" + "static inline int top_decl();\n" + "static inline\n" + "int top_defn() {\n" + " ++x;\n" + " return 0;\n" + "}", + Style); + + Style.BreakBeforeReturnType = FormatStyle::BBRTS_All; + + Style.AttributeMacros = {"__always_inline"}; + verifyFormat("__always_inline\n" + "void f(void);", + Style); + + Style.AttributeMacros = {"__always_inline", "LIBC_INLINE"}; + verifyFormat("LIBC_INLINE static __always_inline\n" + "int compute(int x);", + Style); + + Style.AttributeMacros = {"ATTRIBUTE_PRINTF"}; + verifyFormat("ATTRIBUTE_PRINTF(1, 2) static\n" + "void log(const char *fmt, ...);", + Style); + + // Same identifier: unconfigured -> not a specifier; configured -> specifier. + Style.AttributeMacros = {}; + verifyFormat("FOO static void f(void);", Style); + Style.AttributeMacros = {"FOO"}; + verifyFormat("FOO static\n" + "void f(void);", + Style); + + Style.AttributeMacros = {"LIBC_INLINE"}; + verifyFormat("[[nodiscard]] __attribute__((pure)) LIBC_INLINE static\n" + "int hash(int k);", + Style); +} + TEST_F(FormatTest, AlwaysBreakBeforeMultilineStrings) { FormatStyle NoBreak = getLLVMStyle(); NoBreak.AlwaysBreakBeforeMultilineStrings = false; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
