MyDeveloperDay created this revision.
MyDeveloperDay added reviewers: HazardyKnusperkeks, owenpan, curdeius, rymiel, 
Eugene.Zelenko.
MyDeveloperDay added projects: clang, clang-format.
Herald added a project: All.
MyDeveloperDay requested review of this revision.

Fixes: #58217

This change is to remove extraneous and unnecessary ';' from after a function 
declaration, its off by default and carries the same "code modification" 
warning as some of our other code manipulating changes.

  int max(int a, int b)
  {
      return a>b?a:b;
  };
  
  class Foo
  {
      int getSomething() const { return something; };
  };

At first, I thought this was a minor problem and not worth anything other than 
using `-Wextra-semi/-Wextra-semi-stmt` as pointed out in the issue comments. 
But clang-format is used by people who may not use the clang compiler, and not 
all compilers have this extra semi warning (AFAIK)

However, I ran this over the clang-codebase and realized (in clang and even the 
clang-format Tests!) how prevalent this is.

This is implemented very much on the same lines as @owenpan does for removing 
the `{}` with RemoveBracesLLVM, there is some repeated code that we could 
rationalize down if accepted. (I didn't want to be too invasive on that work)

This is definitely one of those changes that it would be nice for those of us 
that use "clang-format on save" could get without having to go through a 
compile phase in order to catch the warnings.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D135466

Files:
  clang/docs/ClangFormatStyleOptions.rst
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Format/Format.h
  clang/lib/Format/Format.cpp
  clang/lib/Format/UnwrappedLineParser.cpp
  clang/lib/Format/UnwrappedLineParser.h
  clang/unittests/Format/FormatTest.cpp

Index: clang/unittests/Format/FormatTest.cpp
===================================================================
--- clang/unittests/Format/FormatTest.cpp
+++ clang/unittests/Format/FormatTest.cpp
@@ -20281,6 +20281,7 @@
   CHECK_PARSE_BOOL(Cpp11BracedListStyle);
   CHECK_PARSE_BOOL(ReflowComments);
   CHECK_PARSE_BOOL(RemoveBracesLLVM);
+  CHECK_PARSE_BOOL(RemoveSemiColons);
   CHECK_PARSE_BOOL(SortUsingDeclarations);
   CHECK_PARSE_BOOL(SpacesInParentheses);
   CHECK_PARSE_BOOL(SpacesInSquareBrackets);
@@ -26740,6 +26741,22 @@
                "inline bool var = is_integral_v<T> && is_signed_v<T>;");
 }
 
+TEST_F(FormatTest, RemoveSemiColons) {
+  FormatStyle Style = getLLVMStyle();
+  Style.InsertBraces = true;
+
+  verifyFormat("int max(int a, int b) { return a > b ? a : b; }",
+               "int max(int a, int b) { return a > b ? a : b; };", Style);
+
+  verifyFormat("class Foo {\n"
+               "  int getSomething() const { return something; }\n"
+               "};",
+               "class Foo {\n"
+               "  int getSomething() const { return something; };\n"
+               "};",
+               Style);
+}
+
 } // namespace
 } // namespace format
 } // namespace clang
Index: clang/lib/Format/UnwrappedLineParser.h
===================================================================
--- clang/lib/Format/UnwrappedLineParser.h
+++ clang/lib/Format/UnwrappedLineParser.h
@@ -111,7 +111,8 @@
                           bool KeepBraces = true, IfStmtKind *IfKind = nullptr,
                           bool UnindentWhitesmithsBraces = false,
                           bool CanContainBracedList = true,
-                          TokenType NextLBracesType = TT_Unknown);
+                          TokenType NextLBracesType = TT_Unknown,
+                          bool IsFunctionBlock = false);
   void parseChildBlock(bool CanContainBracedList = true,
                        TokenType NextLBracesType = TT_Unknown);
   void parsePPDirective();
Index: clang/lib/Format/UnwrappedLineParser.cpp
===================================================================
--- clang/lib/Format/UnwrappedLineParser.cpp
+++ clang/lib/Format/UnwrappedLineParser.cpp
@@ -839,7 +839,8 @@
 FormatToken *UnwrappedLineParser::parseBlock(
     bool MustBeDeclaration, unsigned AddLevels, bool MunchSemi, bool KeepBraces,
     IfStmtKind *IfKind, bool UnindentWhitesmithsBraces,
-    bool CanContainBracedList, TokenType NextLBracesType) {
+    bool CanContainBracedList, TokenType NextLBracesType,
+    bool IsFunctionBlock) {
   auto HandleVerilogBlockLabel = [this]() {
     // ":" name
     if (Style.isVerilog() && FormatTok->is(tok::colon)) {
@@ -978,8 +979,17 @@
     parseStructuralElement();
   }
 
-  if (MunchSemi && FormatTok->is(tok::semi))
+  // When this is a function block and there is an unnecessary semicolon
+  // afterwards then mark it as optional (so the RemoveSemi pass can get rid of
+  // it later).
+  if (MunchSemi && FormatTok->is(tok::semi)) {
+    if (IsFunctionBlock) {
+      FormatToken *Previous = Tokens->getPreviousToken();
+      if (Previous && Previous->is(tok::r_brace))
+        FormatTok->Optional = true;
+    }
     nextToken();
+  }
 
   if (PPStartHash == PPEndHash) {
     Line->MatchingOpeningBlockLineIndex = OpeningLineIndex;
@@ -1897,7 +1907,11 @@
           addUnwrappedLine();
         }
         FormatTok->setFinalizedType(TT_FunctionLBrace);
-        parseBlock();
+        parseBlock(/*MustBeDeclaration=*/false, 1u, /*MunchSemi=*/true,
+                   /*KeepBraces=*/true, nullptr,
+                   /*UnindentWhitesmithsBraces=*/false,
+                   /*CanContainBracedList=*/true, TT_Unknown,
+                   /*IsFunctionBlock=*/true);
         addUnwrappedLine();
         return;
       }
Index: clang/lib/Format/Format.cpp
===================================================================
--- clang/lib/Format/Format.cpp
+++ clang/lib/Format/Format.cpp
@@ -852,6 +852,7 @@
     IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment);
     IO.mapOptional("ReflowComments", Style.ReflowComments);
     IO.mapOptional("RemoveBracesLLVM", Style.RemoveBracesLLVM);
+    IO.mapOptional("RemoveSemiColons", Style.RemoveSemiColons);
     IO.mapOptional("RequiresClausePosition", Style.RequiresClausePosition);
     IO.mapOptional("SeparateDefinitionBlocks", Style.SeparateDefinitionBlocks);
     IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines);
@@ -1297,6 +1298,7 @@
   LLVMStyle.UseTab = FormatStyle::UT_Never;
   LLVMStyle.ReflowComments = true;
   LLVMStyle.RemoveBracesLLVM = false;
+  LLVMStyle.RemoveSemiColons = false;
   LLVMStyle.SpacesInParentheses = false;
   LLVMStyle.SpacesInSquareBrackets = false;
   LLVMStyle.SpaceInEmptyBlock = false;
@@ -1915,7 +1917,59 @@
            Token = Token->Next) {
         if (!Token->Optional)
           continue;
-        assert(Token->isOneOf(tok::l_brace, tok::r_brace));
+        if (!Token->isOneOf(tok::l_brace, tok::r_brace))
+          continue;
+        auto Next = Token->Next;
+        assert(Next || Token == Line->Last);
+        if (!Next && NextLine)
+          Next = NextLine->First;
+        SourceLocation Start;
+        if (Next && Next->NewlinesBefore == 0 && Next->isNot(tok::eof)) {
+          Start = Token->Tok.getLocation();
+          Next->WhitespaceRange = Token->WhitespaceRange;
+        } else {
+          Start = Token->WhitespaceRange.getBegin();
+        }
+        const auto Range =
+            CharSourceRange::getCharRange(Start, Token->Tok.getEndLoc());
+        cantFail(Result.add(tooling::Replacement(SourceMgr, Range, "")));
+      }
+    }
+  }
+};
+
+class SemiRemover : public TokenAnalyzer {
+public:
+  SemiRemover(const Environment &Env, const FormatStyle &Style)
+      : TokenAnalyzer(Env, Style) {}
+
+  std::pair<tooling::Replacements, unsigned>
+  analyze(TokenAnnotator &Annotator,
+          SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+          FormatTokenLexer &Tokens) override {
+    AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
+    tooling::Replacements Result;
+    removeSemi(AnnotatedLines, Result);
+    return {Result, 0};
+  }
+
+private:
+  void removeSemi(SmallVectorImpl<AnnotatedLine *> &Lines,
+                  tooling::Replacements &Result) {
+    const auto &SourceMgr = Env.getSourceManager();
+    const auto End = Lines.end();
+    for (auto I = Lines.begin(); I != End; ++I) {
+      const auto Line = *I;
+      removeSemi(Line->Children, Result);
+      if (!Line->Affected)
+        continue;
+      const auto NextLine = I + 1 == End ? nullptr : I[1];
+      for (auto Token = Line->First; Token && !Token->Finalized;
+           Token = Token->Next) {
+        if (!Token->Optional)
+          continue;
+        if (!Token->is(tok::semi))
+          continue;
         auto Next = Token->Next;
         assert(Next || Token == Line->Last);
         if (!Next && NextLine)
@@ -3280,6 +3334,12 @@
       });
     }
 
+    if (Style.RemoveSemiColons) {
+      Passes.emplace_back([&](const Environment &Env) {
+        return SemiRemover(Env, Expanded).process(/*SkipAnnotation=*/true);
+      });
+    }
+
     if (Style.FixNamespaceComments) {
       Passes.emplace_back([&](const Environment &Env) {
         return NamespaceEndCommentsFixer(Env, Expanded).process();
Index: clang/include/clang/Format/Format.h
===================================================================
--- clang/include/clang/Format/Format.h
+++ clang/include/clang/Format/Format.h
@@ -3055,6 +3055,26 @@
   /// \version 14
   bool RemoveBracesLLVM;
 
+  /// Removes unnecessary semicolons from the function braces
+  /// \warning
+  ///  Setting this option to `true` could lead to incorrect code formatting due
+  ///  to clang-format's lack of complete semantic information. As such, extra
+  ///  care should be taken to review code changes made by this option.
+  ///  NOTE:
+  ///  Setting this to false will not add `;` where they were missing
+  /// \endwarning
+  /// \code
+  ///   false:                                     true:
+  ///
+  ///   int max(int a, int b)                      int max(int a, int b)
+  ///   {                                          {
+  ///     return a>b?a:b;                            return a>b?a:b;
+  ///   };                                         }
+  ///
+  /// \endcode
+  /// \version 16
+  bool RemoveSemiColons;
+
   /// \brief The possible positions for the requires clause. The
   /// ``IndentRequires`` option is only used if the ``requires`` is put on the
   /// start of a line.
@@ -3969,6 +3989,7 @@
            RawStringFormats == R.RawStringFormats &&
            ReferenceAlignment == R.ReferenceAlignment &&
            RemoveBracesLLVM == R.RemoveBracesLLVM &&
+           RemoveSemiColons == R.RemoveSemiColons &&
            RequiresClausePosition == R.RequiresClausePosition &&
            SeparateDefinitionBlocks == R.SeparateDefinitionBlocks &&
            ShortNamespaceLines == R.ShortNamespaceLines &&
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -529,6 +529,7 @@
 
 clang-format
 ------------
+- Add `RemoveSemiColons` option for removing unnecessary `;` after a function definition.
 
 clang-extdef-mapping
 --------------------
Index: clang/docs/ClangFormatStyleOptions.rst
===================================================================
--- clang/docs/ClangFormatStyleOptions.rst
+++ clang/docs/ClangFormatStyleOptions.rst
@@ -3758,6 +3758,26 @@
       }
     }
 
+**RemoveSemiColons** (``Boolean``) :versionbadge:`clang-format 16`
+  Removes unnecessary semicolons from the function braces
+
+  .. warning:: 
+
+   Setting this option to `true` could lead to incorrect code formatting due
+   to clang-format's lack of complete semantic information. As such, extra
+   care should be taken to review code changes made by this option.
+   NOTE:
+   Setting this to false will not add `;` where they were missing
+
+  .. code-block:: c++
+
+    false:                                     true:
+
+    int max(int a, int b)                      int max(int a, int b)
+    {                                          {
+      return a>b?a:b;                            return a>b?a:b;
+    };                                         };
+
 **RequiresClausePosition** (``RequiresClausePositionStyle``) :versionbadge:`clang-format 15`
   The position of the ``requires`` clause.
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to