Skip to content

Conversation

@Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Nov 23, 2025

Typedef/using declarations in structs and classes were not created with the native PDB plugin. The following would only create Foo and Foo::Bar:

struct Foo {
    struct Bar {};
    using Baz = Bar;
    using Int = int;
};

With this PR, they're created. One complication is that typedefs and nested types show up identical. The example from above gives:

  0x1006 | LF_FIELDLIST [size = 40, hash = 0x2E844]
           - LF_NESTTYPE [name = `Bar`, parent = 0x1002]
           - LF_NESTTYPE [name = `Baz`, parent = 0x1002]
           - LF_NESTTYPE [name = `Int`, parent = 0x0074 (int)]

To distinguish nested types and typedefs, we check if the parent of a type is equal to the current one (parent(0x1002) == 0x1006) and if the basename matches the nested type name.

@Nerixyz Nerixyz requested a review from ZequanWu November 23, 2025 22:03
@llvmbot llvmbot added the lldb label Nov 23, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 23, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

Typedef/using declarations in structs and classes were not created with the native PDB plugin. The following would only create Foo and Foo::Bar:

struct Foo {
    struct Bar {};
    using Baz = Bar;
    using Int = int;
};

With this PR, they're created. One complication is that typedefs and nested types show up identical. The example from above gives:

  0x1006 | LF_FIELDLIST [size = 40, hash = 0x2E844]
           - LF_NESTTYPE [name = `Bar`, parent = 0x1002]
           - LF_NESTTYPE [name = `Baz`, parent = 0x1002]
           - LF_NESTTYPE [name = `Int`, parent = 0x0074 (int)]

To distinguish nested types and typedefs, we check if the parent of a type is equal to the current one (parent(0x1002) == 0x1006) and if the basename matches the nested type name.


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

3 Files Affected:

  • (modified) lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp (+26)
  • (modified) lldb/test/Shell/SymbolFile/NativePDB/ast-types.cpp (+3-3)
  • (modified) lldb/test/Shell/SymbolFile/NativePDB/nested-types.cpp (+43-14)
diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp
index 46cf9b8524ede..d8e4255b4823f 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp
@@ -233,6 +233,32 @@ Error UdtRecordCompleter::visitKnownMember(
 
 Error UdtRecordCompleter::visitKnownMember(CVMemberRecord &cvr,
                                            NestedTypeRecord &nested) {
+  // Typedefs can only be added on structs.
+  if (m_record.record.kind != Member::Struct)
+    return Error::success();
+
+  clang::QualType qt =
+      m_ast_builder.GetOrCreateType(PdbTypeSymId(nested.Type, false));
+  if (qt.isNull())
+    return Error::success();
+  CompilerType ct = m_ast_builder.ToCompilerType(qt);
+
+  // There's no distinction between nested types and typedefs, so check if we
+  // encountered a nested type.
+  auto *pdb = static_cast<SymbolFileNativePDB *>(
+      m_ast_builder.clang().GetSymbolFile()->GetBackingSymbolFile());
+  std::optional<TypeIndex> parent = pdb->GetParentType(nested.Type);
+  if (parent && *parent == m_id.index && ct.GetTypeName(true) == nested.Name)
+    return Error::success();
+
+  clang::DeclContext *decl_ctx =
+      m_ast_builder.GetOrCreateDeclContextForUid(m_id);
+  if (!decl_ctx)
+    return Error::success();
+
+  std::string name = nested.Name.str();
+  ct.CreateTypedef(name.c_str(), m_ast_builder.ToCompilerDeclContext(*decl_ctx),
+                   0);
   return Error::success();
 }
 
diff --git a/lldb/test/Shell/SymbolFile/NativePDB/ast-types.cpp b/lldb/test/Shell/SymbolFile/NativePDB/ast-types.cpp
index ac0d87e95dbf9..dea124c6d6145 100644
--- a/lldb/test/Shell/SymbolFile/NativePDB/ast-types.cpp
+++ b/lldb/test/Shell/SymbolFile/NativePDB/ast-types.cpp
@@ -175,9 +175,9 @@ int SI::*mp9 = nullptr;
 // CHECK: | |-CXXRecordDecl {{.*}} struct Anonymous<int> definition
 // CHECK: | | `-FieldDecl {{.*}} AnonymousMember 'int'
 // CHECK: | `-CXXRecordDecl {{.*}} struct Anonymous<A::B::C<void>> definition
-// CHECK: |   |-FieldDecl {{.*}} AnonymousMember 'int'
-// CHECK: |   `-CXXRecordDecl {{.*}} struct D definition
-// CHECK: |     `-FieldDecl {{.*}} AnonymousDMember 'int'
+// CHECK: |   |-CXXRecordDecl {{.*}} struct D definition
+// CHECK: |   |  `-FieldDecl {{.*}} AnonymousDMember 'int'
+// CHECK: |   `-FieldDecl {{.*}} AnonymousMember 'int'
 
 int main(int argc, char **argv) {
   AnonInt.AnonymousMember = 1;
diff --git a/lldb/test/Shell/SymbolFile/NativePDB/nested-types.cpp b/lldb/test/Shell/SymbolFile/NativePDB/nested-types.cpp
index f725037a220d9..a4b07cdb1b1b7 100644
--- a/lldb/test/Shell/SymbolFile/NativePDB/nested-types.cpp
+++ b/lldb/test/Shell/SymbolFile/NativePDB/nested-types.cpp
@@ -126,30 +126,59 @@ int main(int argc, char **argv) {
 // CHECK: (lldb) target modules dump ast
 // CHECK: Dumping clang ast for 1 modules.
 // CHECK: TranslationUnitDecl {{.*}}
+
 // CHECK: |-CXXRecordDecl {{.*}} struct S definition
-// CHECK: | |-FieldDecl {{.*}} C 'int'
-// CHECK: | |-FieldDecl {{.*}} D 'int'
-// CHECK: | |-FieldDecl {{.*}} DD 'void *'
 // CHECK: | |-CXXRecordDecl {{.*}} struct NestedStruct definition
 // CHECK: | | |-FieldDecl {{.*}} A 'int'
 // CHECK: | | `-FieldDecl {{.*}} B 'int'
-// CHECK: | `-EnumDecl {{.*}} NestedEnum
-// CHECK: |   |-EnumConstantDecl {{.*}} EnumValue1 'S::NestedEnum'
-// CHECK: |   `-EnumConstantDecl {{.*}} EnumValue2 'S::NestedEnum'
+// CHECK: | |-EnumDecl {{.*}} NestedEnum
+// CHECK: | | |-EnumConstantDecl {{.*}} EnumValue1 'S::NestedEnum'
+// CHECK: | | `-EnumConstantDecl {{.*}} EnumValue2 'S::NestedEnum'
+// CHECK: | |-TypedefDecl {{.*}} VoidPtrT 'void *'
+// CHECK: | | `-PointerType {{.*}} 'void *'
+// CHECK: | |   `-BuiltinType {{.*}} 'void'
+// CHECK: | |-FieldDecl {{.*}} C 'int'
+// CHECK: | |-FieldDecl {{.*}} D 'int'
+// CHECK: | `-FieldDecl {{.*}} DD 'void *'
+
 // CHECK: |-CXXRecordDecl {{.*}} struct T definition
-// CHECK: | |-FieldDecl {{.*}} NT 'int'
+// CHECK: | |-TypedefDecl {{.*}} NestedTypedef 'int'
+// CHECK: | | `-BuiltinType {{.*}} 'int'
+// CHECK: | |-TypedefDecl {{.*}} NestedTypedef2 'S'
+// CHECK: | | `-RecordType {{.*}} 'S' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'S'
 // CHECK: | |-CXXRecordDecl {{.*}} struct NestedStruct definition
 // CHECK: | | |-FieldDecl {{.*}} E 'int'
 // CHECK: | | `-FieldDecl {{.*}} F 'int'
-// CHECK: | `-CXXRecordDecl {{.*}} struct U definition
-// CHECK: |   |-FieldDecl {{.*}} G 'int'
-// CHECK: |   `-FieldDecl {{.*}} H 'int'
+// CHECK: | |-TypedefDecl {{.*}} NestedStructAlias 'T::NestedStruct'
+// CHECK: | | `-RecordType {{.*}} 'T::NestedStruct' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'NestedStruct'
+// CHECK: | |-TypedefDecl {{.*}} NST 'S::NestedStruct'
+// CHECK: | | `-RecordType {{.*}} 'S::NestedStruct' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'NestedStruct'
+// CHECK: | |-CXXRecordDecl {{.*}} struct U definition
+// CHECK: | | |-FieldDecl {{.*}} G 'int'
+// CHECK: | | `-FieldDecl {{.*}} H 'int'
+// CHECK: | `-FieldDecl {{.*}} NT 'int'
+
 // CHECK: |-CXXRecordDecl {{.*}} class U<int> definition
+// CHECK: | |-CXXRecordDecl {{.*}} struct W definition
+// CHECK: | | |-FieldDecl {{.*}} M 'int'
+// CHECK: | | `-FieldDecl {{.*}} N 'int'
+// CHECK: | |-TypedefDecl {{.*}} Y 'U<int>::V<int>'
+// CHECK: | | `-RecordType {{.*}} 'U<int>::V<int>' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'U<int>::V<int>'
+// CHECK: | |-TypedefDecl {{.*}} Z 'U<int>::V<T>'
+// CHECK: | | `-RecordType {{.*}} 'U<int>::V<T>' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'U<int>::V<T>'
 // CHECK: | |-FieldDecl {{.*}} K 'int'
-// CHECK: | |-FieldDecl {{.*}} L 'int'
-// CHECK: | `-CXXRecordDecl {{.*}} struct W definition
-// CHECK: |   |-FieldDecl {{.*}} M 'int'
-// CHECK: |   `-FieldDecl {{.*}} N 'int'
+// CHECK: | `-FieldDecl {{.*}} L 'int'
+
 // CHECK: |-CXXRecordDecl {{.*}} struct U<int>::V<int> definition
+// CHECK: | |-TypedefDecl {{.*}}> W 'int'
+// CHECK: | | `-BuiltinType {{.*}} 'int'
+// CHECK: | |-TypedefDecl {{.*}} X 'U<int>'
+// CHECK: | | `-RecordType {{.*}} 'U<int>' canonical
+// CHECK: | |   `-CXXRecord {{.*}} 'U<int>'
 // CHECK: | |-FieldDecl {{.*}} I 'int'
 // CHECK: | `-FieldDecl {{.*}} J 'int'

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants