Skip to content

Conversation

@sstwcw
Copy link
Contributor

@sstwcw sstwcw commented Oct 22, 2025

Fixes #139514.

Within a long declaration involving the pointer, the program has allowed breaking between the type and the star when the configuration specified that the star should stick to the variable. Now it also allows breaking between the star and the variable when the configuration specified that the star should stick to the type.

Fixes llvm#139514.

Within a long declaration involving the pointer, the program has allowed
breaking between the type and the star when the configuration specified
that the star should stick to the variable.  Now it also allows breaking
between the star and the variable when the configuration specified that
the star should stick to the type.
@llvmbot
Copy link
Member

llvmbot commented Oct 22, 2025

@llvm/pr-subscribers-clang-format

Author: None (sstwcw)

Changes

Fixes #139514.

Within a long declaration involving the pointer, the program has allowed breaking between the type and the star when the configuration specified that the star should stick to the variable. Now it also allows breaking between the star and the variable when the configuration specified that the star should stick to the type.


Full diff: https://github.com/llvm/llvm-project/pull/164686.diff

3 Files Affected:

  • (modified) clang/lib/Format/ContinuationIndenter.cpp (+2-1)
  • (modified) clang/lib/Format/TokenAnnotator.cpp (+4-2)
  • (modified) clang/unittests/Format/FormatTest.cpp (+32)
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index e5abf833194d4..88e80349ad31c 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -1566,7 +1566,8 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) {
   }
 
   if (NextNonComment->isOneOf(TT_StartOfName, TT_PointerOrReference) ||
-      Previous.isOneOf(tok::coloncolon, tok::equal, TT_JsTypeColon)) {
+      Previous.isOneOf(TT_PointerOrReference, tok::coloncolon, tok::equal,
+                       TT_JsTypeColon)) {
     return ContinuationIndent;
   }
   if (PreviousNonComment && PreviousNonComment->is(tok::colon) &&
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 25971d2497f97..3ff5587ab2232 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -4343,7 +4343,7 @@ unsigned TokenAnnotator::splitPenalty(const AnnotatedLine &Line,
       return Style.PenaltyReturnTypeOnItsOwnLine;
     return 200;
   }
-  if (Right.is(TT_PointerOrReference))
+  if (Left.is(TT_PointerOrReference) || Right.is(TT_PointerOrReference))
     return 190;
   if (Right.is(TT_LambdaArrow))
     return 110;
@@ -6262,8 +6262,10 @@ bool TokenAnnotator::canBreakBefore(const AnnotatedLine &Line,
                     TT_ClassHeadName, TT_QtProperty, tok::kw_operator)) {
     return true;
   }
+  // It is fine to break the line when the * or & should be separate from the
+  // name according to the PointerAlignment config.
   if (Left.is(TT_PointerOrReference))
-    return false;
+    return Right.SpacesRequiredBefore;
   if (Right.isTrailingComment()) {
     // We rely on MustBreakBefore being set correctly here as we should not
     // change the "binding" behavior of a comment.
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index ce68f91bef02a..e9a95be9a5358 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -8644,6 +8644,38 @@ TEST_F(FormatTest, BreaksFunctionDeclarations) {
                "                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {}",
                Style);
 
+  Style.ColumnLimit = 70;
+  verifyFormat(
+      "void foo( //\n"
+      "    const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName*\n"
+      "        const my_super_super_super_super_long_variable_name) {}",
+      Style);
+  verifyFormat(
+      "void foo(const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName*\n"
+      "             my_super_super_super_super_long_variable_name) {}",
+      Style);
+  verifyFormat(
+      "void foo(const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName*\n"
+      "             const my_super_super_super_super_long_variable_name) {}",
+      Style);
+
+  Style.PointerAlignment = FormatStyle::PAS_Middle;
+  verifyFormat(
+      "void foo( //\n"
+      "    const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName *\n"
+      "        const my_super_super_super_super_long_variable_name) {}",
+      Style);
+  verifyFormat(
+      "void foo(\n"
+      "    const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName *\n"
+      "        my_super_super_super_super_long_variable_name) {}",
+      Style);
+  verifyFormat(
+      "void foo(\n"
+      "    const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName *\n"
+      "        const my_super_super_super_super_long_variable_name) {}",
+      Style);
+
   Style = getLLVMStyleWithColumns(45);
   Style.PenaltyReturnTypeOnItsOwnLine = 400;
   verifyFormat("template <bool abool, // a comment\n"

Style);

Style.PointerAlignment = FormatStyle::PAS_Middle;
verifyFormat(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be possible to wrap the * now too? Maybe add a test for that? And what happens if the type and the name are both too long to fit * within the same line, do we get something like

void foo(
    Loooooooong
    *
    looooooong);

?

Copy link
Contributor Author

@sstwcw sstwcw Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into it. Now I can't figure out whether the formatter should break the line between the type and the star when middle is configured. I thought that the default right style was for people who read "the expression *x is of type int", while the left and middle styles were for people who read "the variable x is of type int*". Then it seems that the program should avoid breaking between the type and the star when the style is left or middle. However, the code says that the program should break the line between the *const part and the variable when right is selected (pull request #128817, bug report #28919). That seems to contradict my understanding about the styles. What is the reasoning behind the pull request?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know for whom middle is, but never break between the type and the *. And I think #28919 makes a good point, the const belongs to the type.
On the other hand violating the column limit, while there is whitespace which can be a line break is also bad. I don't really know. Maybe add a huge penalty on that?
@owenca any opinion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into it. Now I can't figure out whether the formatter should break the line between the type and the star when middle is configured. I thought that the default right style was for people who read "the expression *x is of type int", while the left and middle styles were for people who read "the variable x is of type int*". Then it seems that the program should avoid breaking between the type and the star when the style is left or middle. However, the code says that the program should break the line between the *const part and the variable when right is selected (pull request #128817, bug report #28919). That seems to contradict my understanding about the styles. What is the reasoning behind the pull request?

The TT_PointerOrReference token (*, &, or &&) before a declarator is part of the type and should not go with the declarator, so wrapping before * doesn't make sense IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know for whom middle is, but never break between the type and the *. And I think #28919 makes a good point, the const belongs to the type. On the other hand violating the column limit, while there is whitespace which can be a line break is also bad. I don't really know. Maybe add a huge penalty on that? @owenca any opinion?

Maybe set CanBreakBefore to false if * is followed by a declarator?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type * const arg and Type * const is too long for the ColumnLimit, where to break? We are within the type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say break before * because const qualifies it.

Comment on lines +8653 to +8654
"void foo(const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName*\n"
" const my_super_super_super_super_long_variable_name) {}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* and const should stay together on the same line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug report says that they should be on separate lines because they can't fit on the same line. I don't see how to satisfy the requirement. Please post the formatted code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +8665 to +8666
" const MySuperSuperSuperSuperSuperSuperSuperSuperLongTypeName *\n"
" const my_super_super_super_super_long_variable_name) {}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

@sstwcw
Copy link
Contributor Author

sstwcw commented Oct 29, 2025

The TT_PointerOrReference token (*, &, or &&) before a declarator is part of the type and should not go with the declarator, so wrapping before * doesn't make sense IMO.

I'd say break before * because const qualifies it.

Do I get it right? The program should prefer breaking after the *const part and fall back to breaking before the *const part when the former violates the column limit, regardless of the PAS configuration.

@sstwcw
Copy link
Contributor Author

sstwcw commented Oct 29, 2025

Why should the *const part bind closer to the type than the variable? After all, if we add the parentheses than we see that the *const x part can be grouped while the int *const part can not.

void foo(const int (*const (x));

That is, why should one think that the second const qualifies the type instead of the variable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attributes and PointerAlignment: Left violates ColumnLimit

4 participants