Skip to content

[clang-format] Regression: ##-pasted argument inside a macro causes odd line breaks in v23 #212835

Description

@joelhock

Summary

clang-format 23 produces lines that exceed ColumnLimit when formatting a
function-like macro body containing a call with an argument preceded by the
## token-paste operator. There are several oddities observed; they perhaps
all have the same root cause.

Environment

  • Good: clang-format version 22.1.0-rc1
  • Bad: clang-format version 23.1.0-rc1
--style='{BasedOnStyle: LLVM, IndentWidth: 4, ContinuationIndentWidth: 8, ColumnLimit: 80, AlignEscapedNewlines: Right}'

Manifestation 1: refusal to insert a line break immediately before the ##-pasted argument

Input:

#define M(f, ...) \
    auto f = call(firstArgumentThatIsQuiteLongEnoughToForceAWrapHere11111111, ##__VA_ARGS__);

v22 (correct — breaks before ##__VA_ARGS__, max column 80):

#define M(f, ...)                                                              \
    auto f = call(firstArgumentThatIsQuiteLongEnoughToForceAWrapHere11111111,  \
                  ##__VA_ARGS__);

v23 (buggy — refuses to break before ##__VA_ARGS__; the last line is 87 columns):

#define M(f, ...)                                                              \
    auto f = call(                                                             \
            firstArgumentThatIsQuiteLongEnoughToForceAWrapHere11111111, ##__VA_ARGS__);

Manifestation 2 — ## in nested logic causes oddities two ways

Here the ##-pasted argument is not the last argument, the call is nested several blocks deep, and its argument list spans multiple lines.

Input (format with the style above):

#define CHECK(flagName, expr, ...) \
    ({ \
        if ((false)) { \
            if (expr) {} \
        } \
        if (unlikely(!(expr))) { \
            if (someLongContainerName.contains(SomeEnumTypeName::flagName)) { \
                someLongContainerName.add(SomeEnumTypeName::flagName); \
            } else { \
                reportFunction(std::source_location::current(), "some diagnostic message string here " "(" #flagName "): " #expr, ##__VA_ARGS__, extra); \
            } \
        } \
        (void) 0; \
    })

v22 (correct — aligned continuation characters):

#define CHECK(flagName, expr, ...)                                             \
    ({                                                                         \
        if ((false)) {                                                         \
            if (expr) {                                                        \
            }                                                                  \
        }                                                                      \
        if (unlikely(!(expr))) {                                               \
            if (someLongContainerName.contains(SomeEnumTypeName::flagName)) {  \
                someLongContainerName.add(SomeEnumTypeName::flagName);         \
            } else {                                                           \
                reportFunction(std::source_location::current(),                \
                               "some diagnostic message string here "          \
                               "(" #flagName "): " #expr,                      \
                               ##__VA_ARGS__, extra);                          \
            }                                                                  \
        }                                                                      \
        (void)0;                                                               \
    })

v23 (buggy - line is not broken at all):

#define CHECK(flagName, expr, ...)                                             \
    ({                                                                         \
        if ((false)) {                                                         \
            if (expr) {                                                        \
            }                                                                  \
        }                                                                      \
        if (unlikely(!(expr))) {                                               \
            if (someLongContainerName.contains(SomeEnumTypeName::flagName)) {  \
                someLongContainerName.add(SomeEnumTypeName::flagName);         \
            } else {                                                           \
                reportFunction(std::source_location::current(), "some diagnostic message string here " "(" #flagName "): " #expr, ##__VA_ARGS__, extra);                                                 \
            }                                                                  \
        }                                                                      \
        (void)0;                                                               \
    })

And if we send v22's output to v23, we get a jagged edge of the continuation character:

#define CHECK(flagName, expr, ...)                                               \
    ({                                                                           \
        if ((false)) {                                                           \
            if (expr) {                                                          \
            }                                                                    \
        }                                                                        \
        if (unlikely(!(expr))) {                                                 \
            if (someLongContainerName.contains(SomeEnumTypeName::flagName)) {    \
                someLongContainerName.add(SomeEnumTypeName::flagName);           \
            } else {                                                             \
                reportFunction(std::source_location::current(),                \
                               "some diagnostic message string here "          \
                               "(" #flagName "): " #expr,                      \
                               ##__VA_ARGS__, extra); \
            }                                                                    \
        }                                                                        \
        (void)0;                                                                 \
    })

Removing just the ## (##__VA_ARGS____VA_ARGS__) makes v23 wrap the call
correctly with a max column of 80, identical to v22.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    Status
    Needs Fix

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions