[clang] [clang-format] Don't re-type finalized tokens before a closing paren (PR #213174)

Tyler Gibbs via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 30 16:57:29 PDT 2026


https://github.com/tylergibbs1 created https://github.com/llvm/llvm-project/pull/213174

`determineStarAmpUsage()` finalizes the second `*` of a chain like `a * b * c` as `TT_BinaryOperator`, so that the whole chain keeps being treated as multiplication:

https://github.com/llvm/llvm-project/blob/17088c9b104e/clang/lib/Format/TokenAnnotator.cpp#L3154-L3158

`modifyContext()` then walks back over the `*`/`&` tokens preceding `)`, `>` or `,` and unconditionally calls `setType(TT_PointerOrReference)` on them, which asserts when the type has already been finalized:

```
Assertion failed: ((!TypeIsFinalized || T == Type) && "Please use overwriteFixedType to change a fixed type."), function setType, file FormatToken.h
```

So formatting `(A*B*)` crashes. The Objective-C case reported in the issue,

```objc
- (void)f:(NSError *__autoreleasing *)e;
```

is the same bug: `determineStarAmpUsage()` guards on `isObjCLifetimeQualifier()`, but `guessLanguage()` annotates a `.h` file as C++ first, where `__autoreleasing` is not recognised as a lifetime qualifier, so the guard does not apply.

This patch skips finalized tokens in that loop, matching the guard already used for the equivalent loop a few lines above (added in 2d0b45bc0a7fcb29aa77242b9262eb7c9eba5333).

Fixes #212870

🤖 Generated with [Claude Code](https://claude.com/claude-code)


>From 3cafb89dc7728540dbea6fbb7281f87a51a74584 Mon Sep 17 00:00:00 2001
From: Tyler Gibbs <tylergibbs049 at gmail.com>
Date: Thu, 30 Jul 2026 17:49:06 -0500
Subject: [PATCH] [clang-format] Don't re-type finalized tokens before a
 closing paren

determineStarAmpUsage() finalizes the second `*` of a chain like `a * b * c`
as TT_BinaryOperator so the whole chain keeps being treated as
multiplication. modifyContext() then walked back over the `*`/`&` tokens
preceding `)`, `>` or `,` and unconditionally called
setType(TT_PointerOrReference) on them, which asserts when the type has
already been finalized.

Formatting `(A*B*)`, or an Objective-C declaration such as
`- (void)f:(NSError *__autoreleasing *)e;`, therefore hit "Please use
overwriteFixedType to change a fixed type.".

Skip finalized tokens in that loop, matching the guard already used for the
equivalent loop a few lines above.

Fixes #212870

Assisted-by: Claude Opus 5 (Claude Code)
---
 clang/lib/Format/TokenAnnotator.cpp   | 3 ++-
 clang/unittests/Format/FormatTest.cpp | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 32ae8990f52c5..9cf894cb97039 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -2336,7 +2336,8 @@ class AnnotatingParser {
       for (FormatToken *Previous = Current.Previous;
            Previous && Previous->isOneOf(tok::star, tok::amp);
            Previous = Previous->Previous) {
-        Previous->setType(TT_PointerOrReference);
+        if (!Previous->isTypeFinalized())
+          Previous->setType(TT_PointerOrReference);
       }
       if (Line.MustBeDeclaration &&
           Contexts.front().ContextType != Context::CtorInitializer) {
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index b72a683ac1fff..22815d608bbcb 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -22563,6 +22563,8 @@ TEST_F(FormatTest, DoNotCrashOnInvalidInput) {
   verifyNoCrash(
       "#xxxx??x<xxxxxxx||??x<xxxxxxx and xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
   verifyNoCrash("a &alias & =");
+  verifyNoCrash("(A*B*)");
+  verifyNoCrash("- (void)f:(NSError *__autoreleasing *)e;");
 }
 
 TEST_F(FormatTest, FormatsTableGenCode) {



More information about the cfe-commits mailing list