Skip to content

Commit 4adae46

Browse files
committed
feat: add template class support to Concerto Reflection
Implement template class reflection with: - Template class declaration and specialization parsing via ClangParser - TemplateClass runtime representation with specialization lookup - Code generation for template classes and their specializations - Header generation for template class declarations - Documentation and test coverage
1 parent 4058247 commit 4adae46

File tree

20 files changed

+1049
-14
lines changed

20 files changed

+1049
-14
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
.vscode
22
.xmake
3+
.cache
34
dist-newstyle
45
vsxmake2022
6+
compile_commands.json
57
build

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,111 @@ Concerto Reflection exposes a rich runtime API. Here are some of the key capab
202202
* All reflection entities (packages, classes, enums, methods, members) can carry key–value attributes. Use `HasAttribute` and `GetAttribute` to inspect them.
203203
* Built‑in types such as `Int32` are also registered with the reflection system, allowing you to treat fundamental types like any other class.
204204

205+
206+
### Template class support
207+
208+
Concerto Reflection supports C++ template classes with full reflection metadata for each specialization.
209+
210+
#### Annotating template classes
211+
212+
Mark template classes with the standard reflection macros:
213+
214+
```cpp
215+
template<typename T>
216+
class CCT_CLASS() Container : public cct::refl::Object
217+
{
218+
public:
219+
Container() : m_value() {}
220+
221+
CCT_MEMBER()
222+
T m_value;
223+
224+
CCT_METHOD()
225+
void SetValue(T val) { m_value = val; }
226+
227+
CCT_METHOD()
228+
T GetValue() const { return m_value; }
229+
230+
CCT_OBJECT(Container);
231+
};
232+
233+
// Explicitly instantiate the specializations you want to reflect
234+
template class Container<int>;
235+
template class Container<double>;
236+
```
237+
238+
#### Accessing template specializations at runtime
239+
240+
Once loaded, you can retrieve specializations in two ways:
241+
242+
**Method 1: Using the TemplateClass API**
243+
244+
```cpp
245+
const auto* containerClass = cct::refl::GetClassByName("cct::sample::Container");
246+
if (containerClass && containerClass->IsTemplateClass())
247+
{
248+
auto* templateClass = dynamic_cast<cct::refl::TemplateClass*>(
249+
const_cast<cct::refl::Class*>(containerClass));
250+
251+
// Get a specific specialization by type string
252+
auto* intSpec = templateClass->GetSpecialization("int"sv);
253+
if (intSpec)
254+
{
255+
auto obj = intSpec->CreateDefaultObject();
256+
// Use the specialized instance...
257+
}
258+
}
259+
```
260+
261+
**Method 2: Direct retrieval by full name**
262+
263+
```cpp
264+
const auto* intSpec = cct::refl::GetClassByName("cct::sample::Container<int>");
265+
if (intSpec)
266+
{
267+
auto obj = intSpec->CreateDefaultObject();
268+
// Use the specialized instance...
269+
}
270+
```
271+
272+
#### Multi-parameter templates
273+
274+
Templates with multiple parameters are fully supported:
275+
276+
```cpp
277+
template<typename K, typename V>
278+
class CCT_CLASS() Pair : public cct::refl::Object
279+
{
280+
public:
281+
CCT_MEMBER()
282+
K m_key;
283+
284+
CCT_MEMBER()
285+
V m_value;
286+
287+
CCT_OBJECT(Pair);
288+
};
289+
290+
// Explicitly instantiate specializations
291+
template class Pair<int, double>;
292+
template class Pair<std::string, int>;
293+
```
294+
295+
Then retrieve them:
296+
297+
```cpp
298+
const auto* pairSpec = cct::refl::GetClassByName("cct::sample::Pair<int,double>");
299+
```
300+
301+
#### How it works
302+
303+
The package generator:
304+
1. Detects template class declarations and their explicit instantiations
305+
2. Generates a base `TemplateClass` that describes the template parameters
306+
3. For each explicit instantiation, generates a specialization class with the concrete type information
307+
4. Registers all specializations in the reflection namespace for runtime discovery
308+
309+
---
205310
---
206311

207312
## 📚 Examples & Documentation

Src/Concerto/PackageGenerator/ClangParser/ClangParser.cpp

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <clang/Tooling/CompilationDatabase.h>
2222
#include <clang/Tooling/Tooling.h>
2323
#include <llvm/ADT/SmallString.h>
24+
#include <clang/AST/PrettyPrinter.h>
2425

2526
using namespace clang;
2627
using namespace clang::tooling;
@@ -218,6 +219,8 @@ namespace cct
218219
for (const auto* D : TU->decls())
219220
ProcessDeclaration(D);
220221

222+
RemoveEmptyNamespaces(m_package.namepsaces);
223+
221224
return &m_package;
222225
}
223226

@@ -393,7 +396,15 @@ namespace cct
393396

394397
void ClangParser::ProcessDeclaration(const Decl* declaration)
395398
{
396-
if (const auto* RD = llvm::dyn_cast<CXXRecordDecl>(declaration))
399+
if (const auto* CTD = llvm::dyn_cast<ClassTemplateDecl>(declaration))
400+
{
401+
ProcessTemplateRecord(CTD);
402+
}
403+
else if (const auto* CTSD = llvm::dyn_cast<ClassTemplateSpecializationDecl>(declaration))
404+
{
405+
ProcessTemplateSpecialization(CTSD);
406+
}
407+
else if (const auto* RD = llvm::dyn_cast<CXXRecordDecl>(declaration))
397408
{
398409
ProcessRecord(RD);
399410
}
@@ -407,7 +418,11 @@ namespace cct
407418
// Visit nested declarations inside namespaces
408419
for (const auto* SD : ND->decls())
409420
{
410-
if (const auto* RD2 = llvm::dyn_cast<CXXRecordDecl>(SD))
421+
if (const auto* CTD2 = llvm::dyn_cast<ClassTemplateDecl>(SD))
422+
ProcessTemplateRecord(CTD2);
423+
else if (const auto* CTSD2 = llvm::dyn_cast<ClassTemplateSpecializationDecl>(SD))
424+
ProcessTemplateSpecialization(CTSD2);
425+
else if (const auto* RD2 = llvm::dyn_cast<CXXRecordDecl>(SD))
411426
ProcessRecord(RD2);
412427
else if (const auto* ED2 = llvm::dyn_cast<EnumDecl>(SD))
413428
ProcessEnum(ED2);
@@ -416,4 +431,205 @@ namespace cct
416431
}
417432
}
418433
}
434+
435+
void ClangParser::ProcessTemplateRecord(const ClassTemplateDecl* templateDeclaration)
436+
{
437+
if (!templateDeclaration)
438+
return;
439+
440+
const auto* recordDecl = templateDeclaration->getTemplatedDecl();
441+
if (!recordDecl || !recordDecl->isThisDeclarationADefinition())
442+
return;
443+
444+
std::optional<std::pair<std::string, TomlAttributes>> classAttr;
445+
for (const auto* A : recordDecl->attrs())
446+
{
447+
std::string scope; TomlAttributes attrs;
448+
if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs))
449+
continue;
450+
if (scope == "cct::Class")
451+
{
452+
classAttr = std::make_pair(scope, attrs);
453+
break;
454+
}
455+
}
456+
457+
if (!classAttr)
458+
return;
459+
460+
Class cls;
461+
cls.name = recordDecl->getNameAsString();
462+
cls.scope = "cct::Class";
463+
cls.tomlAttributes = GetAttributesOr(classAttr->second, {});
464+
cls.isTemplateClass = true;
465+
466+
const auto* paramList = templateDeclaration->getTemplateParameters();
467+
if (paramList)
468+
{
469+
for (unsigned i = 0; i < paramList->size(); ++i)
470+
{
471+
const auto* templateParam = paramList->getParam(i);
472+
::TemplateParameter tparam;
473+
if (const auto* typeParam = llvm::dyn_cast<TemplateTypeParmDecl>(templateParam))
474+
{
475+
tparam.name = typeParam->getNameAsString();
476+
if (typeParam->hasDefaultArgument())
477+
{
478+
// TODO: Extract default argument
479+
}
480+
}
481+
else if (const auto* nonTypeParam = llvm::dyn_cast<NonTypeTemplateParmDecl>(templateParam))
482+
{
483+
tparam.name = nonTypeParam->getNameAsString();
484+
}
485+
486+
if (!tparam.name.empty())
487+
cls.templateParameters.push_back(std::move(tparam));
488+
}
489+
}
490+
491+
if (recordDecl->getNumBases() > 0)
492+
{
493+
const auto& base = *recordDecl->bases_begin();
494+
cls.base = QualTypeToString(base.getType(), *m_astContext);
495+
}
496+
497+
for (const auto* F : recordDecl->fields())
498+
{
499+
bool hasAttr = false;
500+
TomlAttributes memberAttrs;
501+
std::string scope;
502+
for (const auto* A : F->attrs())
503+
{
504+
TomlAttributes attrs;
505+
if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs))
506+
continue;
507+
if (scope == "cct::Member" || scope == "cct::NativeMember")
508+
{
509+
hasAttr = true;
510+
memberAttrs = GetAttributesOr(attrs, {});
511+
break;
512+
}
513+
}
514+
if (!hasAttr) continue;
515+
516+
Class::Member m;
517+
m.name = F->getNameAsString();
518+
m.type = QualTypeToString(F->getType(), *m_astContext);
519+
m.isNative = scope == "cct::NativeMember";
520+
m.tomlAttributes = memberAttrs;
521+
cls.members.push_back(std::move(m));
522+
}
523+
524+
for (const auto* M : recordDecl->methods())
525+
{
526+
bool hasAttr = false;
527+
TomlAttributes methodAttrs;
528+
for (const auto* A : M->attrs())
529+
{
530+
std::string scope; TomlAttributes attrs;
531+
if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs))
532+
continue;
533+
if (scope == "cct::Method")
534+
{
535+
hasAttr = true;
536+
methodAttrs = GetAttributesOr(attrs, {});
537+
break;
538+
}
539+
}
540+
if (!hasAttr) continue;
541+
542+
Class::Method mm;
543+
mm.name = M->getNameAsString();
544+
mm.returnValue = QualTypeToString(M->getReturnType(), *m_astContext);
545+
mm.tomlAttributes = methodAttrs;
546+
547+
unsigned pi = 0;
548+
for (const auto* P : M->parameters())
549+
{
550+
Class::Method::Params param;
551+
param.type = QualTypeToString(P->getType(), *m_astContext);
552+
if (P->getIdentifier()) param.name = P->getNameAsString();
553+
else { param.name = std::string("arg") + std::to_string(pi); }
554+
mm.params.push_back(std::move(param));
555+
++pi;
556+
}
557+
cls.methods.push_back(std::move(mm));
558+
}
559+
560+
std::vector<std::string> nsChain = GetNamespaceChain(recordDecl->getDeclContext());
561+
InsertClassIntoPackage(m_package, nsChain, cls);
562+
}
563+
564+
void ClangParser::ProcessTemplateSpecialization(const ClassTemplateSpecializationDecl* specializationDeclaration)
565+
{
566+
if (!specializationDeclaration || !specializationDeclaration->isThisDeclarationADefinition())
567+
return;
568+
569+
const auto& templateArgs = specializationDeclaration->getTemplateArgs();
570+
std::vector<std::string> typeArgs;
571+
572+
for (unsigned i = 0; i < templateArgs.size(); ++i)
573+
{
574+
const auto& arg = templateArgs[i];
575+
if (arg.getKind() == TemplateArgument::Type)
576+
{
577+
typeArgs.push_back(QualTypeToString(arg.getAsType(), *m_astContext));
578+
}
579+
else if (arg.getKind() == TemplateArgument::Integral)
580+
{
581+
llvm::SmallString<32> s;
582+
arg.getAsIntegral().toString(s, /*Radix=*/10);
583+
typeArgs.emplace_back(s.begin(), s.end());
584+
}
585+
}
586+
587+
const auto* templateDecl = specializationDeclaration->getSpecializedTemplate();
588+
if (!templateDecl)
589+
return;
590+
591+
const auto templateClassName = templateDecl->getNameAsString();
592+
593+
std::vector<std::string> nsChain = GetNamespaceChain(specializationDeclaration->getDeclContext());
594+
Namespace* leaf = nsChain.empty() ? nullptr : EnsureNamespace(m_package, nsChain);
595+
auto& classList = nsChain.empty() ? m_package.classes : leaf->classes;
596+
597+
auto it = std::ranges::find_if(classList, [&](const Class& c)
598+
{
599+
return c.name == templateClassName && c.isTemplateClass;
600+
});
601+
602+
if (it == classList.end())
603+
return;
604+
605+
it->templateSpecializations.push_back(typeArgs.empty() ? "" :
606+
[&typeArgs]()
607+
{
608+
std::string result;
609+
for (std::size_t i = 0; i < typeArgs.size(); ++i)
610+
{
611+
result += typeArgs[i];
612+
if (i < typeArgs.size() - 1)
613+
result += ",";
614+
}
615+
return result;
616+
}()
617+
);
618+
}
619+
620+
void ClangParser::RemoveEmptyNamespaces(std::vector<Namespace>& namespaces)
621+
{
622+
auto it = namespaces.begin();
623+
while (it != namespaces.end())
624+
{
625+
RemoveEmptyNamespaces(it->namespaces);
626+
627+
bool isEmpty = it->classes.empty() && it->enums.empty() && it->namespaces.empty();
628+
629+
if (isEmpty)
630+
it = namespaces.erase(it);
631+
else
632+
++it;
633+
}
634+
}
419635
}

0 commit comments

Comments
 (0)