llvm / llvm/llvm-project

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

Open
#212,835 1 comment 0 reactions 0 assignees View on GitHub
clang-format confirmed regression:23
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## 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:

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

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

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

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

```c++
#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):

```c++
#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):**

```c++
#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):**
```c++
#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:

```c++
#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.

Contributor guide

Open the contributing guide

Research direction

Reproduce the supplied macro examples with the stated style using clang-format v22 and v23, focusing on calls containing ##__VA_ARGS__. Trace the formatting path for token-paste arguments and compare the output; done means v23 wraps within ColumnLimit 80 and preserves aligned continuation characters like v22.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.