diff --git a/gen/cpp-imitating-naming.d b/gen/cpp-imitating-naming.d new file mode 100644 index 00000000000..a7e77562638 --- /dev/null +++ b/gen/cpp-imitating-naming.d @@ -0,0 +1,233 @@ +//===-- gen/cpp-imitating-naming.d --------------------------------*- D -*-===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// + +module gen.cpp_imitating_naming; + +private string dTemplateToCPlusPlus(const(string) name) @safe pure +{ + import std.string : indexOf, indexOfAny; + + ptrdiff_t start = 0; + ptrdiff_t index = name.indexOf('!'); + + string result; + + if (index != -1) + { + result ~= name[start .. index]; + result ~= '<'; + + start = index + 1; + + if (name[index + 1] == '(') + { + start++; + index++; + + size_t enclosedParentheses = 0; + + while (true) + { + switch (name[index]) + { + case '(': + enclosedParentheses++; + break; + case ')': + enclosedParentheses--; + break; + default: + break; + } + + if (enclosedParentheses == 0) + break; + + if (index == cast(ptrdiff_t) name.length - 1) + break; + + index++; + } + + if (name[index] != ')') + index++; + } + else + { + index = name.indexOfAny(" ,.:<>()[]", start); + + if (index == -1) + index = name.length; + } + + result ~= name[start .. index]; + result ~= '>'; + + start = index; + + if (start != name.length && name[start] == ')') + start++; + } + + if (start != name.length) + result ~= name[start .. $]; + + return result; +} + +private string dArrayToCPlusPlus(const(string) name) @safe pure +{ + import std.ascii : isDigit; + import std.string : indexOf, lastIndexOfAny, replace; + + ptrdiff_t index = name.indexOf('['); + + while (index != -1 && name[index + 1].isDigit) + index = name.indexOf('[', index + 1); + + string result = name; + + if (index > 0) + { + index--; + + if (name[index] == '>' || name[index] == ')') + { + size_t enclosedParentheses = 0; + size_t enclosedChevrons = 0; + + while (true) + { + switch (name[index]) + { + case '(': + enclosedParentheses++; + break; + case ')': + enclosedParentheses--; + break; + case '>': + enclosedChevrons++; + break; + case '<': + enclosedChevrons--; + break; + default: + break; + } + + if (enclosedParentheses == 0 && enclosedChevrons == 0) + break; + + if (index == 0) + break; + + index--; + } + } + + if (index > 0) + { + index = name.lastIndexOfAny(" ,<(", index - 1); + index = index != -1 ? index + 1 : 0; + } + + ptrdiff_t bracketsStart = name.indexOf('[', index); + ptrdiff_t bracketsIndex = bracketsStart; + + size_t enclosedSquareBrackets = 0; + + while (true) + { + switch (name[bracketsIndex]) + { + case '[': + enclosedSquareBrackets++; + break; + case ']': + enclosedSquareBrackets--; + break; + default: + break; + } + + if (enclosedSquareBrackets == 0) + break; + + if (bracketsIndex == cast(ptrdiff_t) name.length - 1) + break; + + bracketsIndex++; + } + + bracketsIndex++; + + immutable string search = name[index .. bracketsIndex]; + immutable string value = name[index .. bracketsStart]; + + if (name[bracketsIndex - 1] == ']') + bracketsIndex--; + + bracketsStart++; + + immutable string key = name[bracketsStart .. bracketsIndex]; + + if (key.length == 0) + { + immutable string replaceString = "slice<" ~ value ~ ">"; + result = name.replace(search, replaceString); + } + else + { + immutable string pairKeyValue = key ~ ", " ~ value; + immutable string replaceString = "associative_array<" ~ pairKeyValue ~ ">"; + result = name.replace(search, replaceString); + } + } + + return result; +} + +private string convertDToCPlusPlus(alias identifierModifier)(const(string) name) @safe pure +{ + string result = name; + string previousResult; + + do + { + previousResult = result; + result = identifierModifier(result); + } + while (result != previousResult); + + return result; +} + +/// +string convertDIdentifierToCPlusPlus(const(string) name) @safe pure +{ + import std.string : replace; + + string result = name.replace(".", "::"); + + result = result.convertDToCPlusPlus!dTemplateToCPlusPlus; + result = result.convertDToCPlusPlus!dArrayToCPlusPlus; + + return result; +} + +/// +extern (C++, ldc) +const(char)* convertDIdentifierToCPlusPlus(const(char)* name, size_t nameLength) @trusted pure +{ + import std.exception : assumeUnique; + import std.string : toStringz; + + return name[0 .. nameLength].assumeUnique.convertDIdentifierToCPlusPlus.toStringz; +} diff --git a/gen/dibuilder.cpp b/gen/dibuilder.cpp index b2770fd6d5a..8c11f1017cb 100644 --- a/gen/dibuilder.cpp +++ b/gen/dibuilder.cpp @@ -42,6 +42,9 @@ using DIFlags = llvm::DINode; namespace ldc { +// in gen/cpp-imitating-naming.d +const char *convertDIdentifierToCPlusPlus(const char *name, size_t nameLength); + namespace { #if LDC_LLVM_VER >= 400 const auto DIFlagZero = DIFlags::FlagZero; @@ -65,6 +68,12 @@ const char *getTemplateInstanceName(TemplateInstance *ti) { return name; } +llvm::StringRef processDIName(llvm::StringRef name) { + return global.params.symdebug == 2 + ? convertDIdentifierToCPlusPlus(name.data(), name.size()) + : name; +} + } // namespace bool DIBuilder::mustEmitFullDebugInfo() { @@ -165,7 +174,7 @@ llvm::StringRef DIBuilder::GetNameAndScope(Dsymbol *sym, DIScope &scope) { scope = GetSymbolScope(sym); } - return name; + return processDIName(name); } // Sets the memory address for a debuginfo variable. @@ -318,12 +327,13 @@ DIType DIBuilder::CreateEnumType(Type *type) { subscripts.push_back(Subscript); } - llvm::StringRef Name = te->sym->toChars(); - unsigned LineNumber = te->sym->loc.linnum; - DIFile File(CreateFile(te->sym)); + DIScope scope = nullptr; + const auto name = GetNameAndScope(te->sym, scope); + const auto lineNumber = te->sym->loc.linnum; + const auto file = CreateFile(te->sym); return DBuilder.createEnumerationType( - GetSymbolScope(te->sym), Name, File, LineNumber, + scope, name, file, lineNumber, getTypeAllocSize(T) * 8, // size (bits) getABITypeAlign(T) * 8, // align (bits) DBuilder.getOrCreateArray(subscripts), // subscripts @@ -347,14 +357,15 @@ DIType DIBuilder::CreatePointerType(Type *type) { const llvm::Optional DWARFAddressSpace = llvm::None; #endif + const auto name = processDIName(type->toPrettyChars(true)); + return DBuilder.createPointerType(CreateTypeDescription(nt), getTypeAllocSize(T) * 8, // size (bits) getABITypeAlign(T) * 8, // align (bits) #if LDC_LLVM_VER >= 500 DWARFAddressSpace, #endif - type->toPrettyChars(true) // name - ); + name); } DIType DIBuilder::CreateVectorType(Type *type) { @@ -419,6 +430,20 @@ DIType DIBuilder::CreateComplexType(Type *type) { uniqueIdent(t)); // UniqueIdentifier } +DIType DIBuilder::CreateTypedef(unsigned linnum, Type *type, DIFile file, + const char *c_name) { + Type *t = type->toBasetype(); + + // translate functions to function pointers + if (t->ty == Tfunction) + t = t->pointerTo(); + + // find base type + DIType basetype = CreateTypeDescription(t); + + return DBuilder.createTypedef(basetype, c_name, file, linnum, GetCU()); +} + DIType DIBuilder::CreateMemberType(unsigned linnum, Type *type, DIFile file, const char *c_name, unsigned offset, Prot::Kind prot, bool isStatic, @@ -615,19 +640,16 @@ DIType DIBuilder::CreateArrayType(Type *type) { Type *t = type->toBasetype(); assert(t->ty == Tarray); - DIFile file = CreateFile(); - DIScope scope = type->toDsymbol(nullptr) - ? GetSymbolScope(type->toDsymbol(nullptr)) - : GetCU(); + const auto scope = GetCU(); + const auto name = processDIName(type->toPrettyChars(true)); + const auto file = CreateFile(); LLMetadata *elems[] = { CreateMemberType(0, Type::tsize_t, file, "length", 0, Prot::public_), CreateMemberType(0, t->nextOf()->pointerTo(), file, "ptr", global.params.is64bit ? 8 : 4, Prot::public_)}; - return DBuilder.createStructType(scope, - type->toChars(), // Name - file, // File + return DBuilder.createStructType(scope, name, file, 0, // LineNo getTypeAllocSize(T) * 8, // size in bits getABITypeAlign(T) * 8, // alignment in bits @@ -670,7 +692,34 @@ DIType DIBuilder::CreateSArrayType(Type *type) { } DIType DIBuilder::CreateAArrayType(Type *type) { - return CreatePointerType(Type::tvoidptr); + llvm::Type *T = DtoType(type); + Type *t = type->toBasetype(); + assert(t->ty == Taarray); + + TypeAArray *typeAArray = static_cast(t); + + Type *index = typeAArray->index; + Type *value = typeAArray->nextOf(); + + const auto scope = GetCU(); + const auto name = processDIName(type->toPrettyChars(true)); + const auto file = CreateFile(); + + LLMetadata *elems[] = { + CreateTypedef(0, index, file, "__key_t"), + CreateTypedef(0, value, file, "__val_t"), + CreateMemberType(0, Type::tvoidptr, file, "ptr", 0, Prot::public_)}; + + return DBuilder.createStructType(scope, name, file, + 0, // LineNo + getTypeAllocSize(T) * 8, // size in bits + getABITypeAlign(T) * 8, // alignment in bits + DIFlagZero, // What here? + getNullDIType(), // derived from + DBuilder.getOrCreateArray(elems), + 0, // RunTimeLang + getNullDIType(), // VTableHolder + uniqueIdent(t)); // UniqueIdentifier } //////////////////////////////////////////////////////////////////////////////// @@ -708,19 +757,17 @@ DIType DIBuilder::CreateDelegateType(Type *type) { llvm::Type *T = DtoType(type); auto t = static_cast(type); - DICompileUnit CU(GetCU()); - assert(CU && "Compilation unit missing or corrupted"); - auto file = CreateFile(); + const auto scope = GetCU(); + const auto name = processDIName(type->toPrettyChars(true)); + const auto file = CreateFile(); LLMetadata *elems[] = { CreateMemberType(0, Type::tvoidptr, file, "context", 0, Prot::public_), CreateMemberType(0, t->next, file, "funcptr", global.params.is64bit ? 8 : 4, Prot::public_)}; - return DBuilder.createStructType(CU, // compile unit where defined - type->toChars(), // name - file, // file where defined - 0, // line number where defined + return DBuilder.createStructType(scope, name, file, + 0, // line number where defined getTypeAllocSize(T) * 8, // size in bits getABITypeAlign(T) * 8, // alignment in bits DIFlagZero, // flags @@ -845,7 +892,7 @@ void DIBuilder::EmitCompileUnit(Module *m) { llvm::DEBUG_METADATA_VERSION); CUNode = DBuilder.createCompileUnit( - global.params.symdebug == 2 ? llvm::dwarf::DW_LANG_C + global.params.symdebug == 2 ? llvm::dwarf::DW_LANG_C_plus_plus : llvm::dwarf::DW_LANG_D, #if LDC_LLVM_VER >= 400 DBuilder.createFile(llvm::sys::path::filename(srcpath), @@ -872,18 +919,21 @@ DIModule DIBuilder::EmitModule(Module *m) { if (irm->diModule) return irm->diModule; + const auto name = processDIName(m->toPrettyChars(true)); + irm->diModule = DBuilder.createModule( CUNode, - m->toPrettyChars(true), // qualified module name - llvm::StringRef(), // (clang modules specific) ConfigurationMacros - llvm::StringRef(), // (clang modules specific) IncludePath - llvm::StringRef() // (clang modules specific) ISysRoot + name, // qualified module name + llvm::StringRef(), // (clang modules specific) ConfigurationMacros + llvm::StringRef(), // (clang modules specific) IncludePath + llvm::StringRef() // (clang modules specific) ISysRoot ); return irm->diModule; } DINamespace DIBuilder::EmitNamespace(Dsymbol *sym, llvm::StringRef name) { + name = processDIName(name); const bool exportSymbols = true; return DBuilder.createNameSpace(GetSymbolScope(sym), name #if LDC_LLVM_VER < 500 diff --git a/gen/dibuilder.h b/gen/dibuilder.h index 972a5173f3d..9c31040c89e 100644 --- a/gen/dibuilder.h +++ b/gen/dibuilder.h @@ -179,6 +179,8 @@ class DIBuilder { DIType CreatePointerType(Type *type); DIType CreateVectorType(Type *type); DIType CreateComplexType(Type *type); + DIType CreateTypedef(unsigned linnum, Type *type, DIFile file, + const char *c_name); DIType CreateMemberType(unsigned linnum, Type *type, DIFile file, const char *c_name, unsigned offset, Prot::Kind, bool isStatic = false, DIScope scope = nullptr); diff --git a/tests/debuginfo/args_cdb.d b/tests/debuginfo/args_cdb.d index 4f9c6d27fc5..8393ec6a62a 100644 --- a/tests/debuginfo/args_cdb.d +++ b/tests/debuginfo/args_cdb.d @@ -1,10 +1,17 @@ - // REQUIRES: Windows // REQUIRES: cdb -// RUN: %ldc -g -of=%t.exe %s + +// -g: +// RUN: %ldc -g -of=%t_g.exe %s // RUN: sed -e "/^\\/\\/ CDB:/!d" -e "s,// CDB:,," %s \ -// RUN: | %cdb -snul -lines -y . %t.exe >%t.out -// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=%arch < %t.out +// RUN: | %cdb -snul -lines -y . %t_g.exe >%t_g.out +// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=%arch -check-prefix=CHECK-G -check-prefix=CHECK-G-%arch < %t_g.out + +// -gc: +// RUN: %ldc -gc -of=%t_gc.exe %s +// RUN: sed -e "/^\\/\\/ CDB:/!d" -e "s,// CDB:,," %s \ +// RUN: | %cdb -snul -lines -y . %t_gc.exe >%t_gc.out +// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=%arch -check-prefix=CHECK-GC -check-prefix=CHECK-GC-%arch < %t_gc.out module args_cdb; import core.simd; @@ -24,7 +31,7 @@ int byValue(ubyte ub, ushort us, uint ui, ulong ul, Small small, Large large, TypeInfo_Class ti, typeof(null) np) { -// CDB: bp `args_cdb.d:27` +// CDB: bp `args_cdb.d:34` // CDB: g // arguments implicitly passed by reference aren't shown if unused float cim = c.im + fa[7] + dg() + small.val + large.a; @@ -44,8 +51,10 @@ int byValue(ubyte ub, ushort us, uint ui, ulong ul, // x64: int delegate() * dg = // x86: int delegate() dg = // CHECK: * fun = {{0x[0-9a-f`]*}} -// x86: struct int[] slice = -// CHECK: unsigned char * aa = {{0x[0-9a-f`]*}} +// CHECK-G-x86: struct int[] slice = +// CHECK-GC-x86: struct slice slice = +// CHECK-G: struct float[int] aa = +// CHECK-GC: struct associative_array aa = // x64: unsigned char (*)[16] fa // x86: unsigned char [16] fa // x86: float [4] f4 = float [4] @@ -57,7 +66,8 @@ int byValue(ubyte ub, ushort us, uint ui, ulong ul, // CHECK: void * np = {{0x[0`]*}} // params emitted as locals (listed after params) for Win64: -// x64: struct int[] slice +// CHECK-G-x64: struct int[] slice = +// CHECK-GC-x64: struct slice slice = // x64: float [4] f4 = float [4] // x64: double [4] d4 = double [4] @@ -74,7 +84,8 @@ int byValue(ubyte ub, ushort us, uint ui, ulong ul, // CHECK-SAME: args_cdb.main.__lambda // CDB: ?? slice -// CHECK: struct int[] +// CHECK-G: struct int[] +// CHECK-GC: struct slice // CHECK-NEXT: length : 2 // CHECK-NEXT: ptr : // CHECK-SAME: 0n10 @@ -101,7 +112,8 @@ int byValue(ubyte ub, ushort us, uint ui, ulong ul, // CDB: ?? ti // CHECK: object::TypeInfo_Class -// CHECK-NEXT: m_init : byte[] +// CHECK-G-NEXT: m_init : byte[] +// CHECK-GC-NEXT: m_init : slice } int byPtr(ubyte* ub, ushort* us, uint* ui, ulong* ul, @@ -112,7 +124,7 @@ int byPtr(ubyte* ub, ushort* us, uint* ui, ulong* ul, Small* small, Large* large, TypeInfo_Class* ti, typeof(null)* np) { -// CDB: bp `args_cdb.d:115` +// CDB: bp `args_cdb.d:127` // CDB: g return 3; // CHECK: !args_cdb::byPtr @@ -143,12 +155,14 @@ int byPtr(ubyte* ub, ushort* us, uint* ui, ulong* ul, // CDB: ?? *fun // CHECK: * // CDB: ?? *slice -// CHECK: struct int[] +// CHECK-G: struct int[] +// CHECK-GC: struct slice // CHECK-NEXT: length : 2 // CHECK-NEXT: ptr : // CHECK-SAME: 0n10 // CDB: ?? *aa -// CHECK: unsigned char * {{0x[0-9a-f`]*}} +// CHECK-G: struct float[int] +// CHECK-GC: struct associative_array // CDB: ?? (*fa)[1] // CHECK: unsigned char 0x0e // CDB: ?? (*f4)[1] @@ -164,7 +178,8 @@ int byPtr(ubyte* ub, ushort* us, uint* ui, ulong* ul, // CHECK-NEXT: b : // CDB: ?? *ti // CHECK: struct object::TypeInfo_Class -// CHECK-NEXT: m_init : byte[] +// CHECK-G-NEXT: m_init : byte[] +// CHECK-GC-NEXT: m_init : slice // CDB: ?? *np // CHECK: void * {{0x[0`]*}} } @@ -177,7 +192,7 @@ int byRef(ref ubyte ub, ref ushort us, ref uint ui, ref ulong ul, ref Small small, ref Large large, ref TypeInfo_Class ti, ref typeof(null) np) { -// CDB: bp `args_cdb.d:180` +// CDB: bp `args_cdb.d:195` // CDB: g // CHECK: !args_cdb::byRef @@ -209,13 +224,15 @@ int byRef(ref ubyte ub, ref ushort us, ref uint ui, ref ulong ul, // CDB: ?? *fun // CHECK: * {{0x[0-9a-f`]*}} // CDB: ?? *slice -// CHECK: struct int[] +// CHECK-G: struct int[] +// CHECK-GC: struct slice // CHECK-NEXT: length : 2 // CHECK-NEXT: ptr : {{0x[0-9a-f`]*}} -> 0n10 // CDB: ?? (*fa)[1] // CHECK: unsigned char 0x0e // CDB: ?? *aa -// CHECK: unsigned char * {{0x[0-9a-f`]*}} +// CHECK-G: struct float[int] +// CHECK-GC: struct associative_array // CDB: ?? (*f4)[1] // CHECK: float 16 // CDB: ?? (*d4)[2] @@ -229,7 +246,8 @@ int byRef(ref ubyte ub, ref ushort us, ref uint ui, ref ulong ul, // CHECK-NEXT: b : // CDB: ?? *ti // CHECK: struct object::TypeInfo_Class * {{0x[0-9a-f`]*}} -// CHECK-NEXT: m_init : byte[] +// CHECK-G-NEXT: m_init : byte[] +// CHECK-GC-NEXT: m_init : slice // CDB: ?? *np // no-CHECK: void * {{0x[0`]*}} (not available) @@ -280,7 +298,7 @@ int main() Large large = Large(19); TypeInfo_Class ti = typeid(TypeInfo); typeof(null) np = null; - + byValue(ub, us, ui, ul, f, d, r, c, dg, fun, slice, aa, fa, f4, d4, small, large, ti, np); byPtr(&ub, &us, &ui, &ul, &f, &d, &r, &c, &dg, &fun, &slice, &aa, &fa, &f4, &d4, &small, &large, &ti, &np); byRef(ub, us, ui, ul, f, d, r, c, dg, fun, slice, aa, fa, f4, d4, small, large, ti, np); diff --git a/tests/debuginfo/scopes_cdb.d b/tests/debuginfo/scopes_cdb.d index d5ab1f54589..e90f086cdb8 100644 --- a/tests/debuginfo/scopes_cdb.d +++ b/tests/debuginfo/scopes_cdb.d @@ -1,9 +1,17 @@ // REQUIRES: Windows // REQUIRES: cdb -// RUN: %ldc -g -of=%t.exe %s + +// -g: +// RUN: %ldc -g -of=%t_g.exe %s +// RUN: sed -e "/^\\/\\/ CDB:/!d" -e "s,// CDB:,," %s \ +// RUN: | %cdb -snul -lines -y . %t_g.exe >%t_g.out +// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-G < %t_g.out + +// -gc: +// RUN: %ldc -gc -of=%t_gc.exe %s // RUN: sed -e "/^\\/\\/ CDB:/!d" -e "s,// CDB:,," %s \ -// RUN: | %cdb -snul -lines -y . %t.exe >%t.out -// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=%arch < %t.out +// RUN: | %cdb -snul -lines -y . %t_gc.exe >%t_gc.out +// RUN: FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-GC < %t_gc.out module scopes_cdb; @@ -16,9 +24,10 @@ template Template(int N) int[N] field; void foo() { -// CDB: bp `scopes_cdb.d:19` +// CDB: bp `scopes_cdb.d:27` // CDB: g -// CHECK: !scopes_cdb::Template!1::foo+ +// CHECK-G: !scopes_cdb::Template!1::foo+ +// CHECK-GC: !scopes_cdb::Template<1>::foo+ } } @@ -26,7 +35,7 @@ extern (C++, cppns) { void cppFoo() { -// CDB: bp `scopes_cdb.d:29` +// CDB: bp `scopes_cdb.d:38` // CDB: g // CHECK: !scopes_cdb::cppns::cppFoo+ } @@ -34,9 +43,10 @@ extern (C++, cppns) void templatedFoo(int N)() { -// CDB: bp `scopes_cdb.d:37` +// CDB: bp `scopes_cdb.d:46` // CDB: g -// CHECK: !scopes_cdb::templatedFoo!2+ +// CHECK-G: !scopes_cdb::templatedFoo!2+ +// CHECK-GC: !scopes_cdb::templatedFoo<2>+ } mixin template Mixin(T) @@ -44,7 +54,7 @@ mixin template Mixin(T) T mixedInField; void mixedInFoo() { -// CDB: bp `scopes_cdb.d:47` +// CDB: bp `scopes_cdb.d:57` // CDB: g // CHECK: !scopes_cdb::S::mixedInFoo+ } @@ -71,9 +81,10 @@ void test() T[N] field; void foo() { -// CDB: bp `scopes_cdb.d:74` +// CDB: bp `scopes_cdb.d:84` // CDB: g -// CHECK: !scopes_cdb::test::TemplatedNestedStruct!(S, 3)::foo+ +// CHECK-G: !scopes_cdb::test::TemplatedNestedStruct!(S, 3)::foo+ +// CHECK-GC: !scopes_cdb::test::TemplatedNestedStruct::foo+ } } @@ -85,7 +96,7 @@ void test() int field; void foo() { -// CDB: bp `scopes_cdb.d:88` +// CDB: bp `scopes_cdb.d:99` // CDB: g // CHECK: !scopes_cdb::test::NestedStruct::foo+ } @@ -94,12 +105,13 @@ void test() NestedStruct ns; ns.foo(); -// CDB: bp `scopes_cdb.d:97` +// CDB: bp `scopes_cdb.d:108` // CDB: g // CDB: dv /t -// CHECK: struct scopes_cdb::S s = -// CHECK-NEXT: struct scopes_cdb::test::TemplatedNestedStruct!(S, 3) tns = -// CHECK-NEXT: struct scopes_cdb::test::NestedStruct ns = +// CHECK: struct scopes_cdb::S s = +// CHECK-G-NEXT: struct scopes_cdb::test::TemplatedNestedStruct!(S, 3) tns = +// CHECK-GC-NEXT: struct scopes_cdb::test::TemplatedNestedStruct tns = +// CHECK-NEXT: struct scopes_cdb::test::NestedStruct ns = } void main()