Author: Adrian Vogelsgesang
Date: 2026-01-16T19:53:03+01:00
New Revision: 0ee7accf6610325c94cec5270b42f4160a4116fc

URL: 
https://github.com/llvm/llvm-project/commit/0ee7accf6610325c94cec5270b42f4160a4116fc
DIFF: 
https://github.com/llvm/llvm-project/commit/0ee7accf6610325c94cec5270b42f4160a4116fc.diff

LOG: [clangd] Support `=default` in DefineOutline to find insertion point 
(#175618)

Since #128164, the DefineOutline tweak is looking for a good insertion
point by looking at where neighboring functions are defined. That
heuristic didn't yet handle `= default`. This commit adds support for
it.

Added: 
    

Modified: 
    clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
    clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp 
b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
index 45e7adeeefcd9..761ab013374ef 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -671,6 +671,27 @@ class DefineOutline : public Tweak {
     return {};
   }
 
+  // Helper function to skip over a matching pair of tokens (e.g., parentheses
+  // or braces). Returns an iterator to the matching closing token, or the end
+  // iterator if no matching pair is found.
+  template <typename Iterator>
+  Iterator skipTokenPair(Iterator Begin, Iterator End, tok::TokenKind 
StartKind,
+                         tok::TokenKind EndKind) {
+    int Count = 0;
+    for (auto It = Begin; It != End; ++It) {
+      if (It->kind() == StartKind) {
+        ++Count;
+      } else if (It->kind() == EndKind) {
+        if (--Count == 0)
+          return It;
+        if (Count < 0)
+          // We encountered a closing token without a matching opening token.
+          return End;
+      }
+    }
+    return End;
+  }
+
   // We don't know the actual start or end of the definition, only the position
   // of the name. Therefore, we heuristically try to locate the last token
   // before or in this function, respectively. Adapt as required by user code.
@@ -718,34 +739,41 @@ class DefineOutline : public Tweak {
       InsertBefore();
     } else {
       // Skip over one top-level pair of parentheses (for the parameter list)
-      // and one pair of curly braces (for the code block).
+      // and one pair of curly braces (for the code block), or `= default`.
       // If that fails, insert before the function instead.
       auto Tokens =
           syntax::tokenize(syntax::FileRange(SM.getMainFileID(), *StartOffset,
                                              Buffer.getBuffer().size()),
                            SM, AST->getLangOpts());
-      bool SkippedParams = false;
-      int OpenParens = 0;
-      int OpenBraces = 0;
       std::optional<syntax::Token> Tok;
-      for (const auto &T : Tokens) {
-        tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;
-        tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;
-        int &Count = SkippedParams ? OpenBraces : OpenParens;
-        if (T.kind() == StartKind) {
-          ++Count;
-        } else if (T.kind() == EndKind) {
-          if (--Count == 0) {
-            if (SkippedParams) {
-              Tok = T;
-              break;
+
+      // Skip parameter list (parentheses)
+      auto ClosingParen = skipTokenPair(Tokens.begin(), Tokens.end(),
+                                        tok::l_paren, tok::r_paren);
+      if (ClosingParen != Tokens.end()) {
+        // After the closing paren, check for `= default`
+        auto AfterParams = std::next(ClosingParen);
+        if (AfterParams != Tokens.end() && AfterParams->kind() == tok::equal) {
+          auto NextIt = std::next(AfterParams);
+          if (NextIt != Tokens.end() && NextIt->kind() == tok::kw_default) {
+            // Find the semicolon after `default`.
+            auto SemiIt = std::next(NextIt);
+            if (SemiIt != Tokens.end() && SemiIt->kind() == tok::semi) {
+              Tok = *SemiIt;
             }
-            SkippedParams = true;
-          } else if (Count < 0) {
-            break;
+          }
+        }
+
+        // If not `= default`, skip function body (braces)
+        if (!Tok && AfterParams != Tokens.end()) {
+          auto ClosingBrace = skipTokenPair(AfterParams, Tokens.end(),
+                                            tok::l_brace, tok::r_brace);
+          if (ClosingBrace != Tokens.end()) {
+            Tok = *ClosingBrace;
           }
         }
       }
+
       if (Tok)
         InsertionLoc = Tok->endLocation();
       else

diff  --git a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp 
b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
index 7689bf3181a5f..df7d251afc3e4 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/DefineOutlineTests.cpp
@@ -950,6 +950,37 @@ inline void Foo::neighbor() {}
 )cpp",
           {}},
 
+      // Adjacent definition with `= default`
+      {
+          R"cpp(
+struct Foo {
+  void ignored1();
+  Foo();
+  void fun^c() {}
+  void ignored2();
+};
+)cpp",
+          R"cpp(
+#include "a.hpp"
+void Foo::ignored1() {}
+Foo::Foo() = default;
+void Foo::ignored2() {}
+)cpp",
+          R"cpp(
+struct Foo {
+  void ignored1();
+  Foo();
+  void func() ;
+  void ignored2();
+};
+)cpp",
+          R"cpp(
+#include "a.hpp"
+void Foo::ignored1() {}
+Foo::Foo() = default;void Foo::func() {}
+
+void Foo::ignored2() {}
+)cpp"},
   };
 
   for (const auto &Case : Cases) {


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to