[clang-format] Regression: `##`-pasted argument inside a macro causes odd line breaks in v23
- 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
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