llvm / llvm/llvm-project

[clang-format] Spurious spaces around template brackets when callee is UDL operator (`operator""_x<...>()`)

Open
#210,135 5 comments 0 reactions 1 assignee Claimed by @johnnyb2543 View on GitHub
clang-format
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Hi! I ran into this issue using clang-format 17, where it was treating variadic user-defined literals (UDLs) strangely, adding extra spaces. I had Claude help me explore the behavior across different versions. Here's what we found.

First, this is a regression: clang-format 14, 15, and 16 all format the construct correctly. It looks like it got partially fixed in clang 19, but is still "half present" even in the latest release.

## Minimal reproducer

Input (`--style=LLVM`, but style-independent):

```cpp
template auto f() { return operator""_mag(); }
```

### Actual output

clang-format **17.0.6** and **18.1.8**:

```cpp
template auto f() { return operator""_mag < Cs... > (); }
```

clang-format **19.1.7** through **22.1.8** (latest):

```cpp
template auto f() { return operator""_mag < Cs...>(); }
```

(19+ stopped spacing the closing `>`, but still inserts a spurious space before `<`.)

### Expected output

The pre-17 behavior, unchanged from an ordinary template call:

```cpp
template auto f() { return operator""_mag(); }
```

### Control (correct in all versions)

Replacing the UDL operator with a normal function name is formatted correctly everywhere, confirming the operator-literal name is what triggers the bug:

```cpp
template auto f() { return g(); } // stays g()
```

## Version bisection

| clang-format | Output | Status |
|---|---|---|
| 14.0.6 | `operator""_mag()` | correct |
| 15.0.7 | `operator""_mag()` | correct |
| 16.0.6 | `operator""_mag()` | correct |
| 17.0.6 | `operator""_mag < Cs... > ()` | **regressed (first bad)** |
| 18.1.8 | `operator""_mag < Cs... > ()` | broken |
| 19.1.7 | `operator""_mag < Cs...>()` | partially fixed, still wrong |
| 20.1.8 | `operator""_mag < Cs...>()` | partially fixed, still wrong |
| 21.1.8 | `operator""_mag < Cs...>()` | partially fixed, still wrong |
| 22.1.8 | `operator""_mag < Cs...>()` | partially fixed, still wrong |

(Binaries from the PyPI `clang-format` wheels.)

## Notes

This looks related to the "unnecessary spaces around template brackets" family of regressions (#110968, #112487, #123144), but those cover other triggers (`conditional_t<...>{}`, binary operators inside the argument list). None of them involve a user-defined-literal operator as the template-id name, and the `operator""` case remains unfixed on the latest release. The distinguishing feature here is purely the `operator""` name preceding the `<...>()`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.