Skip to content

[clang-format] a lambda with a qualified trailing return type is split after :: when its body breaks a << chain with comments #241859

Open

Description

@aivazis

Summary

clang-format 23.1.1 breaks the line after the :: of a lambda's qualified trailing return type and
indents the whole body to the column of the unqualified name, when the body contains a << chain
whose operands are separated by comment lines. The same lambda formats as expected when its return
type has no scope, or when the chain carries no comments.

Reproduction

The .clang-format of the project where it was found is reproduced below; with it,
the input below reproduces the problem. A configuration reduced to BasedOnStyle: LLVM plus the
project's column limit, indentation and operator settings does not, so the trigger involves another
of the project's options, not yet isolated.

input.cc:

void
f(cls_t & cls)
{
    cls.def("iq", [](const DataSet & self) -> py::object {
        auto channel = make();
        channel
            // where
            << here()
            // flush
            << endl;
        return none();
    });
}

Expected

The input unchanged, apart from the placement of the arguments of def.

Actual

    cls.def(
        "iq",
        [](const DataSet & self) -> py::
                                     object {
                                         auto channel = make();
                                         ...

Comments in the body are then wrapped against the column limit from the new, deeper indentation.

Observations

  • -> py::object, -> pybind11::object and -> ::pybind11::object all trigger it; -> object_t,
    an alias without a scope, does not.
  • Removing the comment lines from the << chain makes it go away; so does keeping the comments and
    using a named function with the same trailing return type and body.

Version

clang-format version 23.1.1 (conda-forge), macOS 27, arm64.

the project's .clang-format
# -*- yaml -*-
# -*- coding: utf-8 -*-
#
# michael a.g. aïvázis <michael.aivazis@para-sim.com>
# (c) 1998-2026 all rights reserved


---
Language:        Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: false
AlignEscapedNewlines: true
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterCaseLabel: false
  AfterClass: false
  AfterControlStatement: Never
  AfterEnum: false
  AfterFunction: true
  AfterNamespace: false
  AfterStruct: false
  AfterUnion: false
  AfterExternBlock: false
  BeforeCatch: false
  BeforeElse: false
  BeforeLambdaBody: false
  BeforeWhile: false
  IndentBraces: false
  SplitEmptyFunction: false
  SplitEmptyRecord: false
  SplitEmptyNamespace: false
BreakAfterReturnType: TopLevel
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
BreakStringLiterals: true
BreakTemplateDeclarations: Yes
ColumnLimit: 100
CommentPragmas: ''
CompactNamespaces: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: true
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeBlocks: Preserve
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertNewlineAtEOF: true
InsertTrailingCommas: None
KeepEmptyLines:
  AtEndOfFile:     false
  AtStartOfBlock:  false
  AtStartOfFile:   true
LineEnding:      LF
MacroBlockBegin: ''
MacroBlockEnd:   ''
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
PackConstructorInitializers: CurrentLine
PenaltyBreakAssignment: 1
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 1000
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 1
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Middle
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInParens: Never
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: Never
...


# end of file

Activity

  1. llvmorg-github-actions commented on Nov 12, 2026

    @llvmorg-github-actions

    @llvm/issue-subscribers-clang-format

    Author: Michael Aivazis (aivazis)

    Details ## Summary

    clang-format 23.1.1 breaks the line after the :: of a lambda's qualified trailing return type and
    indents the whole body to the column of the unqualified name, when the body contains a &lt;&lt; chain
    whose operands are separated by comment lines. The same lambda formats as expected when its return
    type has no scope, or when the chain carries no comments.

    Reproduction

    The .clang-format of the project where it was found is reproduced below; with it,
    the input below reproduces the problem. A configuration reduced to BasedOnStyle: LLVM plus the
    project's column limit, indentation and operator settings does not, so the trigger involves another
    of the project's options, not yet isolated.

    input.cc:

    void
    f(cls_t &amp; cls)
    {
        cls.def("iq", [](const DataSet &amp; self) -&gt; py::object {
            auto channel = make();
            channel
                // where
                &lt;&lt; here()
                // flush
                &lt;&lt; endl;
            return none();
        });
    }

    Expected

    The input unchanged, apart from the placement of the arguments of def.

    Actual

        cls.def(
            "iq",
            [](const DataSet &amp; self) -&gt; py::
                                         object {
                                             auto channel = make();
                                             ...

    Comments in the body are then wrapped against the column limit from the new, deeper indentation.

    Observations

    • -&gt; py::object, -&gt; pybind11::object and -&gt; ::pybind11::object all trigger it; -&gt; object_t,
      an alias without a scope, does not.
    • Removing the comment lines from the &lt;&lt; chain makes it go away; so does keeping the comments and
      using a named function with the same trailing return type and body.

    Version

    clang-format version 23.1.1 (conda-forge), macOS 27, arm64.

    <details><summary>the project's <code>.clang-format</code></summary>

    # -*- yaml -*-
    # -*- coding: utf-8 -*-
    #
    # michael a.g. aïvázis &lt;michael.aivazis@<!-- -->para-sim.com&gt;
    # (c) 1998-2026 all rights reserved
    
    
    ---
    Language:        Cpp
    AccessModifierOffset: -4
    AlignAfterOpenBracket: AlwaysBreak
    AlignConsecutiveAssignments: false
    AlignConsecutiveBitFields: false
    AlignConsecutiveDeclarations: false
    AlignConsecutiveMacros: false
    AlignEscapedNewlines: true
    AlignOperands: AlignAfterOperator
    AlignTrailingComments: true
    AllowAllArgumentsOnNextLine: true
    AllowAllParametersOfDeclarationOnNextLine: true
    AllowShortBlocksOnASingleLine: false
    AllowShortCaseLabelsOnASingleLine: false
    AllowShortEnumsOnASingleLine: true
    AllowShortFunctionsOnASingleLine: Inline
    AllowShortIfStatementsOnASingleLine: Never
    AllowShortLambdasOnASingleLine: Inline
    AllowShortLoopsOnASingleLine: false
    AlwaysBreakBeforeMultilineStrings: true
    BinPackArguments: true
    BinPackParameters: true
    BraceWrapping:
      AfterCaseLabel: false
      AfterClass: false
      AfterControlStatement: Never
      AfterEnum: false
      AfterFunction: true
      AfterNamespace: false
      AfterStruct: false
      AfterUnion: false
      AfterExternBlock: false
      BeforeCatch: false
      BeforeElse: false
      BeforeLambdaBody: false
      BeforeWhile: false
      IndentBraces: false
      SplitEmptyFunction: false
      SplitEmptyRecord: false
      SplitEmptyNamespace: false
    BreakAfterReturnType: TopLevel
    BreakBeforeBinaryOperators: NonAssignment
    BreakBeforeBraces: Custom
    BreakBeforeTernaryOperators: false
    BreakConstructorInitializers: AfterColon
    BreakInheritanceList: AfterColon
    BreakStringLiterals: true
    BreakTemplateDeclarations: Yes
    ColumnLimit: 100
    CommentPragmas: ''
    CompactNamespaces: true
    ConstructorInitializerIndentWidth: 4
    ContinuationIndentWidth: 4
    Cpp11BracedListStyle: false
    DerivePointerAlignment: false
    DisableFormat: false
    FixNamespaceComments: true
    ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
    IncludeBlocks: Preserve
    IndentCaseBlocks: false
    IndentCaseLabels: true
    IndentExternBlock: AfterExternBlock
    IndentGotoLabels: true
    IndentPPDirectives: None
    IndentWidth: 4
    IndentWrappedFunctionNames: false
    InsertNewlineAtEOF: true
    InsertTrailingCommas: None
    KeepEmptyLines:
      AtEndOfFile:     false
      AtStartOfBlock:  false
      AtStartOfFile:   true
    LineEnding:      LF
    MacroBlockBegin: ''
    MacroBlockEnd:   ''
    MaxEmptyLinesToKeep: 2
    NamespaceIndentation: All
    PackConstructorInitializers: CurrentLine
    PenaltyBreakAssignment: 1
    PenaltyBreakBeforeFirstCallParameter: 1
    PenaltyBreakComment: 300
    PenaltyBreakFirstLessLess: 1000
    PenaltyBreakString: 1000
    PenaltyBreakTemplateDeclaration: 1
    PenaltyExcessCharacter: 1000000
    PenaltyReturnTypeOnItsOwnLine: 200
    PointerAlignment: Middle
    ReflowComments: true
    SortIncludes: false
    SortUsingDeclarations: false
    SpaceAfterCStyleCast: true
    SpaceAfterLogicalNot: false
    SpaceAfterTemplateKeyword: true
    SpaceBeforeAssignmentOperators: true
    SpaceBeforeCpp11BracedList: true
    SpaceBeforeCtorInitializerColon: true
    SpaceBeforeInheritanceColon: true
    SpaceBeforeParens: ControlStatements
    SpaceBeforeRangeBasedForLoopColon: true
    SpaceBeforeSquareBrackets: false
    SpacesBeforeTrailingComments: 1
    SpacesInAngles: false
    SpacesInContainerLiterals: true
    SpacesInParens: Never
    SpacesInSquareBrackets: false
    Standard: c++20
    TabWidth: 4
    UseTab: Never
    ...
    
    
    # end of file

    </details>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions