https://github.com/dingxiangfei2009 created 
https://github.com/llvm/llvm-project/pull/230070

Use of initializer should constitute the use of the base type. It appears that 
we have not been recording such use sites which potentially go indirectly 
through incomplete forward declarations.

>From 099a16fbc23801e1c7e10f73b1b81adfa760dbba Mon Sep 17 00:00:00 2001
From: Xiangfei Ding <[email protected]>
Date: Thu, 8 Oct 2026 10:44:43 +0000
Subject: [PATCH] [include-cleaner] Trace through incomplete forward
 declaration

Use of initializer should constitute the use of the base type.
It appears that we have not been recording such use, potentially
indirectly through incomplete forward declaration.


Signed-off-by: Xiangfei Ding <[email protected]>
---
 .../include-cleaner/lib/WalkAST.cpp           | 33 +++++++++++++--
 .../unittests/AnalysisTest.cpp                | 18 ++++++++
 .../include-cleaner/unittests/WalkASTTest.cpp | 41 +++++++++++++++++++
 3 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp 
b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index 5449411371e60..be385a5411762 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -101,10 +101,14 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
   NamedDecl *getMemberProvider(QualType Base) {
     if (Base->isPointerType())
       return getMemberProvider(Base->getPointeeType());
-    if (const auto *TT = dyn_cast<TypedefType>(Base))
-      return TT->getDecl();
-    if (const auto *UT = dyn_cast<UsingType>(Base))
-      return UT->getDecl();
+    // An alias of an incomplete record does not provide the record definition.
+    if (const auto *RT = Base->getAs<RecordType>();
+        !RT || RT->getDecl()->isCompleteDefinition()) {
+      if (const auto *TT = dyn_cast<TypedefType>(Base))
+        return TT->getDecl();
+      if (const auto *UT = dyn_cast<UsingType>(Base))
+        return UT->getDecl();
+    }
     // A heuristic: to resolve a template type to **only** its template name.
     // We're only using this method for the base type of MemberExpr, in general
     // the template provides the member, and the critical case 
`unique_ptr<Foo>`
@@ -248,6 +252,27 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
     return true;
   }
 
+  bool VisitInitListExpr(InitListExpr *E) {
+    const InitListExpr *Sem = E->isSemanticForm() ? E : E->getSemanticForm();
+    QualType Type = Sem ? Sem->getType() : E->getType();
+    report(E->getBeginLoc(), getMemberProvider(Type), RefType::Implicit);
+    return true;
+  }
+
+  bool VisitCXXParenListInitExpr(CXXParenListInitExpr *E) {
+    report(E->getBeginLoc(), getMemberProvider(E->getType()),
+           RefType::Implicit);
+    return true;
+  }
+
+  bool VisitDesignatedInitExpr(DesignatedInitExpr *E) {
+    for (const DesignatedInitExpr::Designator &D : E->designators()) {
+      if (D.isFieldDesignator())
+        report(D.getFieldLoc(), D.getFieldDecl(), RefType::Implicit);
+    }
+    return true;
+  }
+
   bool VisitOverloadExpr(OverloadExpr *E) {
     // Since we can't prove which overloads are used, report all of them.
     for (NamedDecl *D : E->decls())
diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp 
b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
index 5df8792c93b2e..22cb9c5abdb24 100644
--- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
@@ -445,6 +445,24 @@ TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) {
   EXPECT_THAT(Results.Unused, testing::IsEmpty());
 }
 
+TEST_F(AnalyzeTest, ForwardDeclaredTypedefAggregateInit) {
+  Inputs.Code = R"cpp(
+#include "fwd.h"
+#include "def.h"
+
+static const Bar b = { 1 };
+)cpp";
+  Inputs.ExtraFiles["fwd.h"] = guard("typedef struct Foo Bar;");
+  Inputs.ExtraFiles["def.h"] = guard("struct Foo { int a; };");
+  TestAST AST(Inputs);
+  auto Decls = AST.context().getTranslationUnitDecl()->decls();
+  auto Results =
+      analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
+              PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
+  EXPECT_THAT(Results.Unused, testing::IsEmpty());
+  EXPECT_THAT(Results.Missing, testing::IsEmpty());
+}
+
 TEST(FixIncludes, Basic) {
   llvm::StringRef Code = R"cpp(#include "d.h"
 #include "a.h"
diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp 
b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
index c9519c2e91b68..519368b23ddac 100644
--- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
@@ -472,6 +472,22 @@ TEST(WalkAST, MemberExprs) {
       namespace ns { template<typename> struct Foo { int a; }; }
       using ns::$implicit^Foo;)cpp",
            "void k(Foo<int> b) { b.^a; }");
+  // If the alias points to an incomplete forward declaration, look through to
+  // the underlying record declaration.
+  testWalk(R"cpp(
+      typedef struct $implicit^Foo Bar;
+      struct Foo { int a; };)cpp",
+           "void test(Bar b) { b.^a; }");
+  testWalk(R"cpp(
+      typedef struct $implicit^Foo Bar;
+      typedef Bar Baz;
+      struct Foo { int a; };)cpp",
+           "void test(Baz b) { b.^a; }");
+  testWalk(R"cpp(
+      namespace ns { struct $implicit^Foo; }
+      using Bar = ns::Foo;
+      namespace ns { struct Foo { int a; }; })cpp",
+           "void test(Bar b) { b.^a; }");
   // Test the dependent-type case (CXXDependentScopeMemberExpr)
   testWalk("template<typename T> struct $implicit^Base { void method(); };",
            "template<typename T> void k(Base<T> t) { t.^method(); }");
@@ -487,6 +503,31 @@ TEST(WalkAST, ConstructExprs) {
   testWalk("struct $implicit^S { S(int); };", "S ^t(42);");
   testWalk("struct $implicit^S { S(int); };", "S t = ^42;");
   testWalk("namespace ns { struct S{}; } using ns::$implicit^S;", "S ^t;");
+  testWalk("typedef struct $implicit^S T; struct S {};", "T ^t;");
+}
+
+TEST(WalkAST, InitListExprs) {
+  testWalk("struct $implicit^S { int a; };", "S s = ^{1};");
+  testWalk("struct $implicit^S { int a; };", "S s^(1);");
+  testWalk("struct S { int $implicit^a; };", "S s = {.^a = 1};");
+  testWalk("struct S { int a; }; using $implicit^T = S;", "T s = ^{1};");
+  testWalk(R"cpp(
+      typedef struct $implicit^Foo Bar;
+      struct Foo { int a; };)cpp",
+           "Bar b = ^{1};");
+  testWalk(R"cpp(
+      typedef struct $implicit^Foo Bar;
+      using Baz = Bar;
+      struct Foo { int a; };)cpp",
+           "Baz b = ^{1};");
+  testWalk(R"cpp(
+      typedef struct $implicit^Foo Bar;
+      struct Foo { int a; };)cpp",
+           "Bar b = ^{.a = 1};");
+  testWalk(R"cpp(
+      typedef struct Foo Bar;
+      struct Foo { int $implicit^a; };)cpp",
+           "Bar b = {.^a = 1};");
 }
 
 TEST(WalkAST, Operator) {

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

Reply via email to