Skip to content

Conversation

@tomekpaszek
Copy link

@tomekpaszek tomekpaszek commented Oct 26, 2023

Sometimes macro definitions contain a layout we don't want to change in any context. This PR adds a new style option IgnorePPDefinitions that prevents clang-format from touching macro definitions.

This PR addresses issue #67991

@tomekpaszek tomekpaszek marked this pull request as ready for review October 27, 2023 12:01
@tomekpaszek tomekpaszek force-pushed the format-ignore-ppdirectives branch from 228301b to 1233f75 Compare October 27, 2023 12:05
@github-actions
Copy link

github-actions bot commented Oct 27, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@rymiel
Copy link
Member

rymiel commented Oct 28, 2023

#67911 is a pull request, not an issue, i'm not sure what you're referring to

Copy link
Contributor

@owenca owenca left a comment

Choose a reason for hiding this comment

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

Please refer to the policy for adding new options.

@owenca
Copy link
Contributor

owenca commented Oct 28, 2023

#67911 is a pull request, not an issue, i'm not sure what you're referring to

I think it was a typo. It should be #67991.

Copy link
Contributor

Choose a reason for hiding this comment

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

let not leave TODO's lets just do it..

Copy link
Author

Choose a reason for hiding this comment

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

The code path for when the statement is preceded by a comment seems different. It's not 100% unclear to me where to look to support that. Any hints would be appriciated

Copy link
Contributor

Choose a reason for hiding this comment

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

I always comment every other check out, and run only this test case in a debugger to look where the change is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually tests with a (line and block) comment after the #define would be interesting too, should they be wrapped? Or the space after // added?

@tru
Copy link
Collaborator

tru commented Nov 10, 2023

@tomekpaszek do you plan to finish this PR? we have been testing it internally on our code and would really like this to land in upstream LLVM.

@tomekpaszek
Copy link
Author

Yes, I just couldn't find time for it. I'll try to look into it within the next few days. Great to hear you there is interest in using it!

@tomekpaszek tomekpaszek force-pushed the format-ignore-ppdirectives branch 2 times, most recently from 851a28a to 4ef0518 Compare November 11, 2023 20:39
Copy link
Contributor

Choose a reason for hiding this comment

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

I always comment every other check out, and run only this test case in a debugger to look where the change is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually tests with a (line and block) comment after the #define would be interesting too, should they be wrapped? Or the space after // added?

@tomekpaszek tomekpaszek force-pushed the format-ignore-ppdirectives branch from 4ef0518 to c5da2c4 Compare November 13, 2023 09:58
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Nov 13, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 13, 2023

@llvm/pr-subscribers-clang

Author: Tomek (tomekpaszek)

Changes

Sometimes macro definitions contain a layout we don't want to change in any context. This PR adds a new style option IgnorePPDefinitions that prevents clang-format from touching PP directives.

This PR addresses issue #67991


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

7 Files Affected:

  • (modified) clang/docs/ClangFormatStyleOptions.rst (+5)
  • (modified) clang/include/clang/Format/Format.h (+5)
  • (modified) clang/lib/Format/Format.cpp (+2)
  • (modified) clang/lib/Format/UnwrappedLineFormatter.cpp (+2)
  • (modified) clang/lib/Format/UnwrappedLineParser.cpp (+8)
  • (modified) clang/unittests/Format/ConfigParseTest.cpp (+1)
  • (modified) clang/unittests/Format/FormatTest.cpp (+47)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 21342e1b89ea866..80565620d8f24bf 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -3160,6 +3160,11 @@ the configuration (without a prefix: ``Auto``).
   For example: `KJ_IF_MAYBE
   <https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md#maybes>`_
 
+.. _IgnorePPDefinitions:
+
+**IgnorePPDefinitions** (``Boolean``) :versionbadge:`clang-format 18` :ref:`¶ <IgnorePPDefinitions>`
+  Ignore formatting in preprocessor definitions.
+
 .. _IncludeBlocks:
 
 **IncludeBlocks** (``IncludeBlocksStyle``) :versionbadge:`clang-format 6` :ref:`¶ <IncludeBlocks>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 3e9d1915badd87f..3af7241441c8b13 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2442,6 +2442,10 @@ struct FormatStyle {
   /// <https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md#maybes>`_
   /// \version 13
   std::vector<std::string> IfMacros;
+      
+  /// Ignore formatting in preprocessor definitions.
+  /// \version 18
+  bool IgnorePPDefinitions;
 
   /// Specify whether access modifiers should have their own indentation level.
   ///
@@ -4719,6 +4723,7 @@ struct FormatStyle {
                R.IncludeStyle.IncludeIsMainRegex &&
            IncludeStyle.IncludeIsMainSourceRegex ==
                R.IncludeStyle.IncludeIsMainSourceRegex &&
+           IgnorePPDefinitions == R.IgnorePPDefinitions &&
            IndentAccessModifiers == R.IndentAccessModifiers &&
            IndentCaseBlocks == R.IndentCaseBlocks &&
            IndentCaseLabels == R.IndentCaseLabels &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index edb33f4af4defef..6e5ec754dfdcdd9 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1000,6 +1000,7 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("FixNamespaceComments", Style.FixNamespaceComments);
     IO.mapOptional("ForEachMacros", Style.ForEachMacros);
     IO.mapOptional("IfMacros", Style.IfMacros);
+    IO.mapOptional("IgnorePPDefinitions", Style.IgnorePPDefinitions);
     IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks);
     IO.mapOptional("IncludeCategories", Style.IncludeStyle.IncludeCategories);
     IO.mapOptional("IncludeIsMainRegex", Style.IncludeStyle.IncludeIsMainRegex);
@@ -1504,6 +1505,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.ForEachMacros.push_back("Q_FOREACH");
   LLVMStyle.ForEachMacros.push_back("BOOST_FOREACH");
   LLVMStyle.IfMacros.push_back("KJ_IF_MAYBE");
+  LLVMStyle.IgnorePPDefinitions = false;
   LLVMStyle.IncludeStyle.IncludeCategories = {
       {"^\"(llvm|llvm-c|clang|clang-c)/", 2, 0, false},
       {"^(<|\"(gtest|gmock|isl|json)/)", 3, 0, false},
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 280485d9a90d1bf..bbf6383ff7673f6 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -1355,6 +1355,8 @@ unsigned UnwrappedLineFormatter::format(
     bool FixIndentation = (FixBadIndentation || ContinueFormatting) &&
                           Indent != TheLine.First->OriginalColumn;
     bool ShouldFormat = TheLine.Affected || FixIndentation;
+    if (Style.IgnorePPDefinitions && TheLine.Type == LT_PreprocessorDirective)
+      ShouldFormat = false;
     // We cannot format this line; if the reason is that the line had a
     // parsing error, remember that.
     if (ShouldFormat && TheLine.Type == LT_Invalid && Status) {
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 488d8dc07b1eae3..07cf966a08ff5e5 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -1134,6 +1134,14 @@ void UnwrappedLineParser::parsePPDefine() {
     return;
   }
 
+  if (Style.IgnorePPDefinitions) {
+    do {
+      nextToken();
+    } while (!eof());
+    addUnwrappedLine();
+    return;
+  }
+
   if (IncludeGuard == IG_IfNdefed &&
       IncludeGuardToken->TokenText == FormatTok->TokenText) {
     IncludeGuard = IG_Defined;
diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp
index f90ed178d99c286..7ce9ae26b36fbb9 100644
--- a/clang/unittests/Format/ConfigParseTest.cpp
+++ b/clang/unittests/Format/ConfigParseTest.cpp
@@ -166,6 +166,7 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
   CHECK_PARSE_BOOL(DerivePointerAlignment);
   CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding");
   CHECK_PARSE_BOOL(DisableFormat);
+  CHECK_PARSE_BOOL(IgnorePPDefinitions);
   CHECK_PARSE_BOOL(IndentAccessModifiers);
   CHECK_PARSE_BOOL(IndentCaseLabels);
   CHECK_PARSE_BOOL(IndentCaseBlocks);
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index b2d84f2ee389551..19cf521107c604b 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24153,6 +24153,53 @@ TEST_F(FormatTest, WhitespaceSensitiveMacros) {
   verifyNoChange("FOO(String-ized&Messy+But: :Still=Intentional);", Style);
 }
 
+TEST_F(FormatTest, IgnorePPDefinitions) {
+  FormatStyle Style = getLLVMStyle();
+  Style.IgnorePPDefinitions = true;
+
+  verifyNoChange("#define  A", Style);
+  verifyNoChange("#define A   b", Style);
+  verifyNoChange("#define A  (  args   )", Style);
+  verifyNoChange("#define A  (  args   )  =  func  (  args  )", Style);
+
+  verifyNoChange("#define A x:", Style);
+  verifyNoChange("#define A a. b", Style);
+    
+  // Surrounded with formatted code
+  verifyFormat("int a;\n"
+               "#define  A  a\n"
+               "int a;",
+               "int  a ;\n"
+               "#define  A  a\n"
+               "int  a ;",
+               Style);
+    
+  // Columns are not broken when a limit is set
+  Style.ColumnLimit = 10;
+  verifyNoChange("#define A a a a a", Style);
+  Style.ColumnLimit = 0;
+    
+  // Multiline definition
+  verifyNoChange("#define A \\\n"
+                 "Line one with spaces  .  \\\n"
+                 " Line two.",
+                   Style);
+  verifyNoChange("#define A \\\n"
+                 "a a \\\n"
+                 "a        \\\na",
+                 Style);
+  Style.AlignEscapedNewlines = FormatStyle::ENAS_Left;
+  verifyNoChange("#define A \\\n"
+                 "a a \\\n"
+                 "a        \\\na",
+                 Style);
+  Style.AlignEscapedNewlines = FormatStyle::ENAS_Right;
+  verifyNoChange("#define A \\\n"
+                 "a a \\\n"
+                 "a        \\\na", 
+                 Style);
+}
+
 TEST_F(FormatTest, VeryLongNamespaceCommentSplit) {
   // These tests are not in NamespaceEndCommentsFixerTest because that doesn't
   // test its interaction with line wrapping

Copy link
Contributor

@mydeveloperday mydeveloperday left a comment

Choose a reason for hiding this comment

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

Something here doesn't feel right

Copy link
Author

@tomekpaszek tomekpaszek left a comment

Choose a reason for hiding this comment

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

Something here doesn't feel right

Could you be more specific?

@owenca owenca changed the title [clang-format] Option to ignore PP directives [clang-format] Option to ignore macro definitions Nov 19, 2023
Copy link
Contributor

@HazardyKnusperkeks HazardyKnusperkeks left a comment

Choose a reason for hiding this comment

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

LGTM, but wait for either of @owenca or @mydeveloperday .

Copy link
Contributor

@owenca owenca left a comment

Choose a reason for hiding this comment

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

Please update ReleaseNotes.rst.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please rename it to SkipMacroDefinition or similar.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (Style.IgnorePPDefinitions && LineContainsPPDefinition(TheLine))
if (Style.IgnorePPDefinitions && TheLine.startsWith(tok::hash, tok::pp_define))

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Ignore formatting in preprocessor definitions.
/// Do not format macro definitions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
FormatStyle Style = getLLVMStyle();
auto Style = getLLVMStyle();

@owenca
Copy link
Contributor

owenca commented Nov 23, 2023

After giving more thoughts to this, I think what we really want is SkipMacroDefinitionBody, which would format the code below:

   # define   A   a. b   //comment
   # define   A( x , y )   ( ( x ) + ( y ) )

To the following:

#define A   a. b // comment
#define A(x, y)   ( ( x ) + ( y ) )

That is, only the body (except comments) of a macro definition is not formatted.

@tomekpaszek
Copy link
Author

After giving more thoughts to this, I think what we really want is SkipMacroDefinitionBody, which would format the code below:
(...)
That is, only the body (except comments) of a macro definition is not formatted.

I understand that:

  • we do want to align the macros (respect IndentPPDirectives)
  • we do want to remove extra whitespaces before, within and right after the first token (#define)
  • we do NOT want to touch the body
  • we do NOT want to break lines

@owenca
Copy link
Contributor

owenca commented Nov 27, 2023

After giving more thoughts to this, I think what we really want is SkipMacroDefinitionBody, which would format the code below:
(...)
That is, only the body (except comments) of a macro definition is not formatted.

I understand that:

  • we do want to align the macros (respect IndentPPDirectives)
  • we do want to remove extra whitespaces before, within and right after the first token (#define)

We want to format e.g. #define FOO and #define BAR(x, y).

  • we do NOT want to touch the body
  • we do NOT want to break lines

We don't want to change line splicing (if any) within the body.

@tomekpaszek tomekpaszek force-pushed the format-ignore-ppdirectives branch from f91b383 to 72acbae Compare November 28, 2023 15:21
@tomekpaszek tomekpaszek requested a review from owenca November 28, 2023 15:22
@tomekpaszek tomekpaszek force-pushed the format-ignore-ppdirectives branch from 72acbae to 75ae623 Compare November 28, 2023 15:48
Comment on lines +1161 to +1167
if (Style.SkipMacroDefinitionBody) {
do {
nextToken();
} while (!eof());
} else {
nextToken();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead, we can simply mark the tokens Finalized (after Line->InMacroBody is set to true below) so that the continuation indenter can be left alone.

Copy link
Author

Choose a reason for hiding this comment

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

Hi!
Maybe it's a little late, but I wanted to let you know that I've tried that but it didn't give the expected results. Didn't have time to dig into this approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

Np!

@owenca owenca removed the clang Clang issues not falling into any other category label Nov 30, 2023
@tru
Copy link
Collaborator

tru commented Jan 18, 2024

@tomekpaszek Hi Tomek, do you think you will have time to work on this soon? Otherwise, I can probably finish it off for you since we also want this feature.

@tomekpaszek
Copy link
Author

@tomekpaszek Hi Tomek, do you think you will have time to work on this soon? Otherwise, I can probably finish it off for you since we also want this feature.

Hi tru,
Thanks for reaching out. Unfortunately I'm too busy to work on this at the moment. Feel free to push it forward.

@tru
Copy link
Collaborator

tru commented Jan 18, 2024

@owenca What's the things that still needs to be addressed for this to land?

owenca added a commit that referenced this pull request Jan 20, 2024
@owenca owenca closed this Jan 20, 2024
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.

7 participants