Skip to content

Conversation

@chaitanyav
Copy link
Contributor

Keep anonymous structs/unions embedded in var declarators

Fixes #61477

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Sep 19, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 19, 2025

@llvm/pr-subscribers-clang

Author: NagaChaitanya Vellanki (chaitanyav)

Changes

Keep anonymous structs/unions embedded in var declarators

Fixes #61477


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

1 Files Affected:

  • (modified) clang/include/clang/ExtractAPI/ExtractAPIVisitor.h (+17-23)
diff --git a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
index 9ea664f57f828..5b7a8266d2092 100644
--- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
+++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
@@ -261,15 +261,9 @@ class ExtractAPIVisitorBase : public RecursiveASTVisitor<Derived> {
         Tag = AT->getElementType()->getAsTagDecl();
       }
     }
-    SmallString<128> TagUSR;
-    clang::index::generateUSRForDecl(Tag, TagUSR);
-    if (auto *Record = llvm::dyn_cast_if_present<TagRecord>(
-            API.findRecordForUSR(TagUSR))) {
-      if (Record->IsEmbeddedInVarDeclarator) {
-        NewRecordContext->stealRecordChain(*Record);
-        API.removeRecord(Record);
-      }
-    }
+    // For anonymous structs/unions embedded in var declarators,
+    // we keep them in the hierarchy (don't remove them)
+    // This preserves the path components while maintaining relationships
   }
 };
 
@@ -566,13 +560,7 @@ bool ExtractAPIVisitorBase<Derived>::VisitNamespaceDecl(
 template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::TraverseRecordDecl(RecordDecl *Decl) {
   bool Ret = Base::TraverseRecordDecl(Decl);
-
-  if (!isEmbeddedInVarDeclarator(*Decl) && Decl->isAnonymousStructOrUnion()) {
-    SmallString<128> USR;
-    index::generateUSRForDecl(Decl, USR);
-    API.removeRecord(USR);
-  }
-
+  // Keep anonymous structs in the hierarchy to preserve path components
   return Ret;
 }
 
@@ -620,13 +608,7 @@ template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::TraverseCXXRecordDecl(
     CXXRecordDecl *Decl) {
   bool Ret = Base::TraverseCXXRecordDecl(Decl);
-
-  if (!isEmbeddedInVarDeclarator(*Decl) && Decl->isAnonymousStructOrUnion()) {
-    SmallString<128> USR;
-    index::generateUSRForDecl(Decl, USR);
-    API.removeRecord(USR);
-  }
-
+  // Keep anonymous structs in the hierarchy to preserve path components
   return Ret;
 }
 
@@ -643,6 +625,11 @@ bool ExtractAPIVisitorBase<Derived>::VisitCXXRecordDecl(
 
   SmallString<128> USR;
   index::generateUSRForDecl(Decl, USR);
+
+  if (auto *USRRecord = API.findRecordForUSR(USR)) {
+    return true;
+  }
+
   PresumedLoc Loc =
       Context.getSourceManager().getPresumedLoc(Decl->getLocation());
   DocComment Comment;
@@ -679,6 +666,13 @@ bool ExtractAPIVisitorBase<Derived>::VisitCXXRecordDecl(
   Record->KindForDisplay = getKindForDisplay(Decl);
   Record->Bases = getBases(Decl);
 
+  // Visit nested records
+  for (const auto *NestedDecl : Decl->decls()) {
+    if (auto *NestedRecord = dyn_cast<CXXRecordDecl>(NestedDecl)) {
+      VisitCXXRecordDecl(NestedRecord);
+    }
+  }
+
   return true;
 }
 

@chaitanyav
Copy link
Contributor Author

Some examples.

Example 1

struct MyStruct {
    struct {
        int count;
    } counts[1];
};
{
  "metadata": {
    "formatVersion": {
      "major": 0,
      "minor": 5,
      "patch": 3
    },
    "generator": "clang version 22.0.0git ([email protected]:chaitanyav/llvm-project.git 9628061e055c9f695ff80f9a74e4f6e524b34993)"
  },
  "module": {
    "name": "",
    "platform": {
      "architecture": "arm64",
      "operatingSystem": {
        "minimumVersion": {
          "major": 11,
          "minor": 0,
          "patch": 0
        },
        "name": "macosx"
      },
      "vendor": "apple"
    }
  },
  "relationships": [
    {
      "kind": "memberOf",
      "source": "c:@S@MyStruct@S@test_nested_types1.h@22",
      "target": "c:@S@MyStruct",
      "targetFallback": "MyStruct"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@MyStruct@S@test_nested_types1.h@22@FI@count",
      "target": "c:@S@MyStruct@S@test_nested_types1.h@22",
      "targetFallback": ""
    },
    {
      "kind": "memberOf",
      "source": "c:@S@MyStruct@FI@counts",
      "target": "c:@S@MyStruct",
      "targetFallback": "MyStruct"
    }
  ],
  "symbols": [
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "struct"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "MyStruct"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@MyStruct"
      },
      "kind": {
        "displayName": "Structure",
        "identifier": "c++.struct"
      },
      "location": {
        "position": {
          "character": 7,
          "line": 0
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "MyStruct"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "MyStruct"
          }
        ],
        "title": "MyStruct"
      },
      "pathComponents": [
        "MyStruct"
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "struct"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@MyStruct@S@test_nested_types1.h@22"
      },
      "kind": {
        "displayName": "Structure",
        "identifier": "c++.struct"
      },
      "location": {
        "position": {
          "character": 4,
          "line": 1
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": ""
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": ""
          }
        ],
        "title": ""
      },
      "pathComponents": [
        "MyStruct",
        ""
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "typeIdentifier",
          "preciseIdentifier": "c:I",
          "spelling": "int"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "count"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@MyStruct@S@test_nested_types1.h@22@FI@count"
      },
      "kind": {
        "displayName": "Instance Property",
        "identifier": "c++.property"
      },
      "location": {
        "position": {
          "character": 12,
          "line": 2
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "count"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "count"
          }
        ],
        "title": "count"
      },
      "pathComponents": [
        "MyStruct",
        "",
        "count"
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "struct"
        },
        {
          "kind": "text",
          "spelling": " { ... } "
        },
        {
          "kind": "identifier",
          "spelling": "counts"
        },
        {
          "kind": "text",
          "spelling": "["
        },
        {
          "kind": "number",
          "spelling": "1"
        },
        {
          "kind": "text",
          "spelling": "];"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@MyStruct@FI@counts"
      },
      "kind": {
        "displayName": "Instance Property",
        "identifier": "c++.property"
      },
      "location": {
        "position": {
          "character": 6,
          "line": 3
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "counts"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "counts"
          }
        ],
        "title": "counts"
      },
      "pathComponents": [
        "MyStruct",
        "counts"
      ]
    }
  ]
}

Example 2

class Outer {
    class Inner1 {
        class DeeplyNested {
            int value;
        };
    };

    struct Inner2 {
        enum Status { READY, PENDING };
    };
};
{
  "metadata": {
    "formatVersion": {
      "major": 0,
      "minor": 5,
      "patch": 3
    },
    "generator": "clang version 22.0.0git ([email protected]:chaitanyav/llvm-project.git 1e6840e67eee77e0ef4538732c0d1ab7d570ba31)"
  },
  "module": {
    "name": "",
    "platform": {
      "architecture": "arm64",
      "operatingSystem": {
        "minimumVersion": {
          "major": 11,
          "minor": 0,
          "patch": 0
        },
        "name": "macosx"
      },
      "vendor": "apple"
    }
  },
  "relationships": [
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner1",
      "target": "c:@S@Outer",
      "targetFallback": "Outer"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner1@S@DeeplyNested",
      "target": "c:@S@Outer@S@Inner1",
      "targetFallback": "Inner1"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner1@S@DeeplyNested@FI@value",
      "target": "c:@S@Outer@S@Inner1@S@DeeplyNested",
      "targetFallback": "DeeplyNested"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner2",
      "target": "c:@S@Outer",
      "targetFallback": "Outer"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner2@E@Status",
      "target": "c:@S@Outer@S@Inner2",
      "targetFallback": "Inner2"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner2@E@Status@READY",
      "target": "c:@S@Outer@S@Inner2@E@Status",
      "targetFallback": "Status"
    },
    {
      "kind": "memberOf",
      "source": "c:@S@Outer@S@Inner2@E@Status@PENDING",
      "target": "c:@S@Outer@S@Inner2@E@Status",
      "targetFallback": "Status"
    }
  ],
  "symbols": [
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "class"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "Outer"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer"
      },
      "kind": {
        "displayName": "Class",
        "identifier": "c++.class"
      },
      "location": {
        "position": {
          "character": 6,
          "line": 0
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "Outer"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "Outer"
          }
        ],
        "title": "Outer"
      },
      "pathComponents": [
        "Outer"
      ]
    },
    {
      "accessLevel": "private",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "class"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "Inner1"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner1"
      },
      "kind": {
        "displayName": "Class",
        "identifier": "c++.class"
      },
      "location": {
        "position": {
          "character": 10,
          "line": 1
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "Inner1"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "Inner1"
          }
        ],
        "title": "Inner1"
      },
      "pathComponents": [
        "Outer",
        "Inner1"
      ]
    },
    {
      "accessLevel": "private",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "class"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "DeeplyNested"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner1@S@DeeplyNested"
      },
      "kind": {
        "displayName": "Class",
        "identifier": "c++.class"
      },
      "location": {
        "position": {
          "character": 14,
          "line": 2
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "DeeplyNested"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "DeeplyNested"
          }
        ],
        "title": "DeeplyNested"
      },
      "pathComponents": [
        "Outer",
        "Inner1",
        "DeeplyNested"
      ]
    },
    {
      "accessLevel": "private",
      "declarationFragments": [
        {
          "kind": "typeIdentifier",
          "preciseIdentifier": "c:I",
          "spelling": "int"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "value"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner1@S@DeeplyNested@FI@value"
      },
      "kind": {
        "displayName": "Instance Property",
        "identifier": "c++.property"
      },
      "location": {
        "position": {
          "character": 16,
          "line": 3
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "value"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "value"
          }
        ],
        "title": "value"
      },
      "pathComponents": [
        "Outer",
        "Inner1",
        "DeeplyNested",
        "value"
      ]
    },
    {
      "accessLevel": "private",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "struct"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "Inner2"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner2"
      },
      "kind": {
        "displayName": "Structure",
        "identifier": "c++.struct"
      },
      "location": {
        "position": {
          "character": 11,
          "line": 7
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "Inner2"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "Inner2"
          }
        ],
        "title": "Inner2"
      },
      "pathComponents": [
        "Outer",
        "Inner2"
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "keyword",
          "spelling": "enum"
        },
        {
          "kind": "text",
          "spelling": " "
        },
        {
          "kind": "identifier",
          "spelling": "Status"
        },
        {
          "kind": "text",
          "spelling": " : "
        },
        {
          "kind": "typeIdentifier",
          "preciseIdentifier": "c:i",
          "spelling": "unsigned int"
        },
        {
          "kind": "text",
          "spelling": ";"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner2@E@Status"
      },
      "kind": {
        "displayName": "Enumeration",
        "identifier": "c++.enum"
      },
      "location": {
        "position": {
          "character": 13,
          "line": 8
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "Status"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "Status"
          }
        ],
        "title": "Status"
      },
      "pathComponents": [
        "Outer",
        "Inner2",
        "Status"
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "identifier",
          "spelling": "READY"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner2@E@Status@READY"
      },
      "kind": {
        "displayName": "Enumeration Case",
        "identifier": "c++.enum.case"
      },
      "location": {
        "position": {
          "character": 22,
          "line": 8
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "READY"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "READY"
          }
        ],
        "title": "READY"
      },
      "pathComponents": [
        "Outer",
        "Inner2",
        "Status",
        "READY"
      ]
    },
    {
      "accessLevel": "public",
      "declarationFragments": [
        {
          "kind": "identifier",
          "spelling": "PENDING"
        }
      ],
      "identifier": {
        "interfaceLanguage": "c++",
        "precise": "c:@S@Outer@S@Inner2@E@Status@PENDING"
      },
      "kind": {
        "displayName": "Enumeration Case",
        "identifier": "c++.enum.case"
      },
      "location": {
        "position": {
          "character": 29,
          "line": 8
        },
        "uri": "file://./test_nested_types1.h"
      },
      "names": {
        "navigator": [
          {
            "kind": "identifier",
            "spelling": "PENDING"
          }
        ],
        "subHeading": [
          {
            "kind": "identifier",
            "spelling": "PENDING"
          }
        ],
        "title": "PENDING"
      },
      "pathComponents": [
        "Outer",
        "Inner2",
        "Status",
        "PENDING"
      ]
    }
  ]
}

@chaitanyav
Copy link
Contributor Author

Working on adding new tests and fixing existing tests

@QuietMisdreavus
Copy link
Contributor

cc @daniel-grumberg, i don't 100% remember the full context or the desired/current state of the world, but i do remember that we went back and forth about the state of "anonymous types in var declarators" a couple times.

@chaitanyav chaitanyav force-pushed the fix_61477 branch 2 times, most recently from 101054c to 6b638a9 Compare September 19, 2025 22:55
 Recursively extract nested types. Keep anonymous structs/unions embedded in var declarators

Fixes llvm#61477
@chaitanyav
Copy link
Contributor Author

@daniel-grumberg please review.

@chaitanyav
Copy link
Contributor Author

@daniel-grumberg gentle ping on the PR.

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

Labels

clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang][ExtractAPI] Nested types are not handled

3 participants