From 8b89b694374189530e8966b37aaf838a7f6bf79a Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 30 Dec 2025 10:50:01 +0100 Subject: [PATCH] feat: add template class support and generic class implementation - Add GenericClass implementation with tests - Prevent implicit instantiation of static members for Container and Pair classes - Add template class support to Concerto Reflection - Update Xmake build configuration --- .gitignore | 2 + README.md | 228 ++++++++++++++ .../ClangParser/ClangParser.cpp | 279 +++++++++++++++++- .../ClangParser/ClangParser.hpp | 5 + .../CppGenerator/CppGenerator.cpp | 266 ++++++++++++++++- .../CppGenerator/CppGenerator.hpp | 2 + Src/Concerto/PackageGenerator/Defines.hpp | 16 + .../HeaderGenerator/HeaderGenerator.cpp | 84 +++++- .../HeaderGenerator/HeaderGenerator.hpp | 2 + Src/Concerto/Reflection/Class/Class.cpp | 5 + Src/Concerto/Reflection/Class/Class.hpp | 4 + Src/Concerto/Reflection/Defines.hpp | 4 + .../Reflection/GenericClass/GenericClass.cpp | 74 +++++ .../Reflection/GenericClass/GenericClass.hpp | 53 ++++ .../Reflection/Object/Object.refl.hpp | 12 +- .../TemplateClass/TemplateClass.cpp | 97 ++++++ .../TemplateClass/TemplateClass.hpp | 59 ++++ .../TemplateClass/TemplateClass.inl | 12 + Src/Tests/Class.cpp | 2 +- Src/Tests/Defines.hpp | 2 + Src/Tests/GenericClass.cpp | 106 +++++++ Src/Tests/Method.cpp | 2 +- Src/Tests/Namespace.cpp | 2 +- Src/Tests/SampleBar.cpp | 1 - Src/Tests/SampleBar.refl.hpp | 74 ++++- Src/Tests/TemplateClass.cpp | 180 +++++++++++ Xmake/rules/cct_cpp_reflect.lua | 8 +- xmake.lua | 3 + 28 files changed, 1554 insertions(+), 30 deletions(-) create mode 100644 Src/Concerto/Reflection/GenericClass/GenericClass.cpp create mode 100644 Src/Concerto/Reflection/GenericClass/GenericClass.hpp create mode 100644 Src/Concerto/Reflection/TemplateClass/TemplateClass.cpp create mode 100644 Src/Concerto/Reflection/TemplateClass/TemplateClass.hpp create mode 100644 Src/Concerto/Reflection/TemplateClass/TemplateClass.inl create mode 100644 Src/Tests/GenericClass.cpp create mode 100644 Src/Tests/TemplateClass.cpp diff --git a/.gitignore b/.gitignore index 8ad34ac..7904530 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .vscode .xmake +.cache dist-newstyle vsxmake2022 +compile_commands.json build \ No newline at end of file diff --git a/README.md b/README.md index 6f81435..df1aea2 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,234 @@ Concerto Reflection exposes a rich runtime API. Here are some of the key capab * All reflection entities (packages, classes, enums, methods, members) can carry key–value attributes. Use `HasAttribute` and `GetAttribute` to inspect them. * Built‑in types such as `Int32` are also registered with the reflection system, allowing you to treat fundamental types like any other class. + +### Template class support + +Concerto Reflection supports C++ template classes with full reflection metadata for each specialization. + +#### Annotating template classes + +Mark template classes with the standard reflection macros: + +```cpp +template +class CCT_CLASS() Container : public cct::refl::Object +{ +public: + Container() : m_value() {} + + CCT_MEMBER() + T m_value; + + CCT_METHOD() + void SetValue(T val) { m_value = val; } + + CCT_METHOD() + T GetValue() const { return m_value; } + + CCT_OBJECT(Container); +}; + +// Explicitly instantiate the specializations you want to reflect +template class Container; +template class Container; +``` + +#### Accessing template specializations at runtime + +Once loaded, you can retrieve specializations in two ways: + +**Method 1: Using the TemplateClass API** + +```cpp +const auto* containerClass = cct::refl::GetClassByName("cct::sample::Container"); +if (containerClass && containerClass->IsTemplateClass()) +{ + auto* templateClass = dynamic_cast( + const_cast(containerClass)); + + // Get a specific specialization by type string + auto* intSpec = templateClass->GetSpecialization("int"sv); + if (intSpec) + { + auto obj = intSpec->CreateDefaultObject(); + // Use the specialized instance... + } +} +``` + +**Method 2: Direct retrieval by full name** + +```cpp +const auto* intSpec = cct::refl::GetClassByName("cct::sample::Container"); +if (intSpec) +{ + auto obj = intSpec->CreateDefaultObject(); + // Use the specialized instance... +} +``` + +#### Multi-parameter templates + +Templates with multiple parameters are fully supported: + +```cpp +template +class CCT_CLASS() Pair : public cct::refl::Object +{ +public: + CCT_MEMBER() + K m_key; + + CCT_MEMBER() + V m_value; + + CCT_OBJECT(Pair); +}; + +// Explicitly instantiate specializations +template class Pair; +template class Pair; +``` + +Then retrieve them: + +```cpp +const auto* pairSpec = cct::refl::GetClassByName("cct::sample::Pair"); +``` + +#### How it works + +The package generator: +1. Detects template class declarations and their explicit instantiations +2. Generates a base `TemplateClass` that describes the template parameters +3. For each explicit instantiation, generates a specialization class with the concrete type information +4. Registers all specializations in the reflection namespace for runtime discovery + +--- + +### Generic class support (runtime-parameterized types) + +Concerto Reflection also supports **generic classes** – classes whose member variables store runtime type parameters. Unlike C++ templates which are resolved at compile-time, generic classes allow you to work with polymorphic type parameters at runtime without requiring explicit specializations. + +#### Key differences: Templates vs Generics + +| Aspect | Template Class | Generic Class | +|--------|---|---| +| **Type Resolution** | Compile-time (instantiation-based) | Runtime (parameter-based) | +| **Specializations** | Explicit instantiations required | Single definition works for all type arguments | +| **Type Safety** | Full compile-time checking | Type safety via `const Class*` pointers | +| **Cross-DLL Compatibility** | Each specialization needs code generation | Single definition works across DLLs | +| **Memory Model** | Separate binary code per specialization | Shared implementation, parameterized data | +| **Use Cases** | Known type combinations at build time | Dynamic, unknown type combinations at runtime | + +#### Annotating generic classes + +Mark a class as generic using `CCT_GENERIC_CLASS()` and annotate type parameter fields with `CCT_GENERIC_TYPE()`: + +```cpp +class CCT_CLASS() CCT_GENERIC_CLASS() GenericContainer : public cct::refl::Object +{ +public: + GenericContainer() : m_elementType(nullptr) {} + + CCT_MEMBER() + CCT_GENERIC_TYPE() + const cct::refl::Class* m_elementType; + + CCT_METHOD() + const cct::refl::Class* GetElementType() const + { + return m_elementType; + } + + CCT_OBJECT(GenericContainer); +}; +``` + +Key points: +- Type parameter fields **must** be of type `const cct::refl::Class*` +- Use `CCT_GENERIC_TYPE()` annotation to mark type parameters +- The macro automatically includes `CCT_MEMBER()` annotation +- Provide getter methods to access type parameters + +#### Using generic classes at runtime + +Generic classes are instantiated with type arguments passed to `CreateDefaultObject()`: + +```cpp +const auto* containerClass = cct::refl::GetClassByName("cct::sample::GenericContainer"); +if (containerClass && containerClass->IsGenericClass()) +{ + auto* genericClass = dynamic_cast( + const_cast(containerClass)); + + // Get a type to use as argument + const auto* int32Class = cct::refl::GetClassByName("cct::refl::Int32"); + + // Create instance with type argument(s) + std::vector typeArgs = { int32Class }; + auto obj = genericClass->CreateDefaultObject( + std::span(typeArgs)); + + if (obj) + { + // Access the type parameter + auto container = dynamic_cast(obj.get()); + const auto* elementType = container->GetElementType(); + // Use the element type information... + } +} +``` + +#### Multi-parameter generics + +Generic classes can have multiple type parameters: + +```cpp +class CCT_CLASS() CCT_GENERIC_CLASS() GenericPair : public cct::refl::Object +{ +public: + GenericPair() : m_keyType(nullptr), m_valueType(nullptr) {} + + CCT_MEMBER() + CCT_GENERIC_TYPE() + const cct::refl::Class* m_keyType; + + CCT_MEMBER() + CCT_GENERIC_TYPE() + const cct::refl::Class* m_valueType; + + CCT_METHOD() + const cct::refl::Class* GetKeyType() const { return m_keyType; } + + CCT_METHOD() + const cct::refl::Class* GetValueType() const { return m_valueType; } + + CCT_OBJECT(GenericPair); +}; +``` + +Instantiate with multiple type arguments: + +```cpp +const auto* int32Class = cct::refl::GetClassByName("cct::refl::Int32"); +const auto* int64Class = cct::refl::GetClassByName("cct::refl::Int64"); + +std::vector typeArgs = { int32Class, int64Class }; +auto obj = genericClass->CreateDefaultObject( + std::span(typeArgs)); +``` + +#### How it works + +The package generator: +1. Detects `CCT_GENERIC_CLASS()` annotations on class declarations +2. Identifies type parameter fields via `CCT_GENERIC_TYPE()` annotations +3. Generates code that stores type parameters in member variables during instantiation +4. Provides `CreateDefaultObject(span)` to pass type arguments at runtime + +--- --- ## 📚 Examples & Documentation diff --git a/Src/Concerto/PackageGenerator/ClangParser/ClangParser.cpp b/Src/Concerto/PackageGenerator/ClangParser/ClangParser.cpp index c9947cc..08e7784 100644 --- a/Src/Concerto/PackageGenerator/ClangParser/ClangParser.cpp +++ b/Src/Concerto/PackageGenerator/ClangParser/ClangParser.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace clang; using namespace clang::tooling; @@ -98,6 +99,8 @@ namespace constexpr std::array knownAnnotations = { "cct::Package"sv, "cct::Class"sv, + "cct::GenericClass"sv, + "cct::GenericType"sv, "cct::Member"sv, "cct::NativeMember"sv, "cct::Method"sv, @@ -107,7 +110,8 @@ namespace std::string_view annotation(AnnotateAttribute->getAnnotation().begin(), AnnotateAttribute->getAnnotation().end()); bool known = false; - for (auto& annotationName : knownAnnotations) + + for (const auto& annotationName : knownAnnotations) { if (annotationName == annotation) { @@ -116,7 +120,7 @@ namespace } } - if (known == false) + if (!known) return false; outScope = annotation; @@ -218,6 +222,8 @@ namespace cct for (const auto* D : TU->decls()) ProcessDeclaration(D); + RemoveEmptyNamespaces(m_package.namepsaces); + return &m_package; } @@ -228,6 +234,7 @@ namespace cct std::optional> classAttr; std::optional> packageAttr; + std::optional> genericClassAttr; for (const auto* A : recordDeclaration->attrs()) { std::string scope; TomlAttributes attrs; @@ -235,9 +242,10 @@ namespace cct continue; if (scope == "cct::Class") classAttr = std::make_pair(scope, attrs); else if (scope == "cct::Package") packageAttr = std::make_pair(scope, attrs); + else if (scope == "cct::GenericClass") genericClassAttr = std::make_pair(scope, attrs); } auto txt = recordDeclaration->getNameAsString(); - if (!classAttr && !packageAttr) + if (!classAttr && !packageAttr && !genericClassAttr) return; // Not a reflection-relevant class if (packageAttr) @@ -258,13 +266,53 @@ namespace cct return; } - if (!classAttr) - return; // Only keep classes with cct::Class + // Determine which type of class this is + if (!classAttr && !genericClassAttr) + return; // Only keep classes with cct::Class or cct::GenericClass Class cls; cls.name = recordDeclaration->getNameAsString(); - cls.scope = "cct::Class"; - cls.tomlAttributes = GetAttributesOr(classAttr->second, {}); + + // Handle generic class + if (genericClassAttr) + { + cls.scope = "cct::GenericClass"; + cls.isGenericClass = true; + cls.tomlAttributes = GetAttributesOr(genericClassAttr->second, {}); + + // Detect explicit type parameters via CCT_GENERIC_TYPE() annotations + for (const auto* F : recordDeclaration->fields()) + { + auto fieldName = F->getNameAsString(); + auto fieldType = QualTypeToString(F->getType(), *m_astContext); + + // Check if field has CCT_GENERIC_TYPE annotation + for (const auto* attr : F->getAttrs()) + { + if (const auto* annotationAttr = dyn_cast(attr)) + { + auto annotation = annotationAttr->getAnnotation().str(); + if (annotation == "cct::GenericType") + { + // Verify field type is "const Class*" + if (fieldType == "const cct::refl::Class *" || + fieldType == "const Class *" || + fieldType == "const cct::refl::Class*" || + fieldType == "const Class*") + { + cls.genericTypeParameterFields.push_back(fieldName); + } + break; + } + } + } + } + } + else + { + cls.scope = "cct::Class"; + cls.tomlAttributes = GetAttributesOr(classAttr->second, {}); + } // Base class (first base) if (recordDeclaration->getNumBases() > 0) @@ -393,7 +441,15 @@ namespace cct void ClangParser::ProcessDeclaration(const Decl* declaration) { - if (const auto* RD = llvm::dyn_cast(declaration)) + if (const auto* CTD = llvm::dyn_cast(declaration)) + { + ProcessTemplateRecord(CTD); + } + else if (const auto* CTSD = llvm::dyn_cast(declaration)) + { + ProcessTemplateSpecialization(CTSD); + } + else if (const auto* RD = llvm::dyn_cast(declaration)) { ProcessRecord(RD); } @@ -407,7 +463,11 @@ namespace cct // Visit nested declarations inside namespaces for (const auto* SD : ND->decls()) { - if (const auto* RD2 = llvm::dyn_cast(SD)) + if (const auto* CTD2 = llvm::dyn_cast(SD)) + ProcessTemplateRecord(CTD2); + else if (const auto* CTSD2 = llvm::dyn_cast(SD)) + ProcessTemplateSpecialization(CTSD2); + else if (const auto* RD2 = llvm::dyn_cast(SD)) ProcessRecord(RD2); else if (const auto* ED2 = llvm::dyn_cast(SD)) ProcessEnum(ED2); @@ -416,4 +476,205 @@ namespace cct } } } + + void ClangParser::ProcessTemplateRecord(const ClassTemplateDecl* templateDeclaration) + { + if (!templateDeclaration) + return; + + const auto* recordDecl = templateDeclaration->getTemplatedDecl(); + if (!recordDecl || !recordDecl->isThisDeclarationADefinition()) + return; + + std::optional> classAttr; + for (const auto* A : recordDecl->attrs()) + { + std::string scope; TomlAttributes attrs; + if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs)) + continue; + if (scope == "cct::Class") + { + classAttr = std::make_pair(scope, attrs); + break; + } + } + + if (!classAttr) + return; + + Class cls; + cls.name = recordDecl->getNameAsString(); + cls.scope = "cct::Class"; + cls.tomlAttributes = GetAttributesOr(classAttr->second, {}); + cls.isTemplateClass = true; + + const auto* paramList = templateDeclaration->getTemplateParameters(); + if (paramList) + { + for (unsigned i = 0; i < paramList->size(); ++i) + { + const auto* templateParam = paramList->getParam(i); + ::TemplateParameter tparam; + if (const auto* typeParam = llvm::dyn_cast(templateParam)) + { + tparam.name = typeParam->getNameAsString(); + if (typeParam->hasDefaultArgument()) + { + // TODO: Extract default argument + } + } + else if (const auto* nonTypeParam = llvm::dyn_cast(templateParam)) + { + tparam.name = nonTypeParam->getNameAsString(); + } + + if (!tparam.name.empty()) + cls.templateParameters.push_back(std::move(tparam)); + } + } + + if (recordDecl->getNumBases() > 0) + { + const auto& base = *recordDecl->bases_begin(); + cls.base = QualTypeToString(base.getType(), *m_astContext); + } + + for (const auto* F : recordDecl->fields()) + { + bool hasAttr = false; + TomlAttributes memberAttrs; + std::string scope; + for (const auto* A : F->attrs()) + { + TomlAttributes attrs; + if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs)) + continue; + if (scope == "cct::Member" || scope == "cct::NativeMember") + { + hasAttr = true; + memberAttrs = GetAttributesOr(attrs, {}); + break; + } + } + if (!hasAttr) continue; + + Class::Member m; + m.name = F->getNameAsString(); + m.type = QualTypeToString(F->getType(), *m_astContext); + m.isNative = scope == "cct::NativeMember"; + m.tomlAttributes = memberAttrs; + cls.members.push_back(std::move(m)); + } + + for (const auto* M : recordDecl->methods()) + { + bool hasAttr = false; + TomlAttributes methodAttrs; + for (const auto* A : M->attrs()) + { + std::string scope; TomlAttributes attrs; + if (!ExtractCctAttribute(A, *m_astContext, *m_sourceManager, *m_langOptions, scope, attrs)) + continue; + if (scope == "cct::Method") + { + hasAttr = true; + methodAttrs = GetAttributesOr(attrs, {}); + break; + } + } + if (!hasAttr) continue; + + Class::Method mm; + mm.name = M->getNameAsString(); + mm.returnValue = QualTypeToString(M->getReturnType(), *m_astContext); + mm.tomlAttributes = methodAttrs; + + unsigned pi = 0; + for (const auto* P : M->parameters()) + { + Class::Method::Params param; + param.type = QualTypeToString(P->getType(), *m_astContext); + if (P->getIdentifier()) param.name = P->getNameAsString(); + else { param.name = std::string("arg") + std::to_string(pi); } + mm.params.push_back(std::move(param)); + ++pi; + } + cls.methods.push_back(std::move(mm)); + } + + std::vector nsChain = GetNamespaceChain(recordDecl->getDeclContext()); + InsertClassIntoPackage(m_package, nsChain, cls); + } + + void ClangParser::ProcessTemplateSpecialization(const ClassTemplateSpecializationDecl* specializationDeclaration) + { + if (!specializationDeclaration || !specializationDeclaration->isThisDeclarationADefinition()) + return; + + const auto& templateArgs = specializationDeclaration->getTemplateArgs(); + std::vector typeArgs; + + for (unsigned i = 0; i < templateArgs.size(); ++i) + { + const auto& arg = templateArgs[i]; + if (arg.getKind() == TemplateArgument::Type) + { + typeArgs.push_back(QualTypeToString(arg.getAsType(), *m_astContext)); + } + else if (arg.getKind() == TemplateArgument::Integral) + { + llvm::SmallString<32> s; + arg.getAsIntegral().toString(s, /*Radix=*/10); + typeArgs.emplace_back(s.begin(), s.end()); + } + } + + const auto* templateDecl = specializationDeclaration->getSpecializedTemplate(); + if (!templateDecl) + return; + + const auto templateClassName = templateDecl->getNameAsString(); + + std::vector nsChain = GetNamespaceChain(specializationDeclaration->getDeclContext()); + Namespace* leaf = nsChain.empty() ? nullptr : EnsureNamespace(m_package, nsChain); + auto& classList = nsChain.empty() ? m_package.classes : leaf->classes; + + auto it = std::ranges::find_if(classList, [&](const Class& c) + { + return c.name == templateClassName && c.isTemplateClass; + }); + + if (it == classList.end()) + return; + + it->templateSpecializations.push_back(typeArgs.empty() ? "" : + [&typeArgs]() + { + std::string result; + for (std::size_t i = 0; i < typeArgs.size(); ++i) + { + result += typeArgs[i]; + if (i < typeArgs.size() - 1) + result += ","; + } + return result; + }() + ); + } + + void ClangParser::RemoveEmptyNamespaces(std::vector& namespaces) + { + auto it = namespaces.begin(); + while (it != namespaces.end()) + { + RemoveEmptyNamespaces(it->namespaces); + + bool isEmpty = it->classes.empty() && it->enums.empty() && it->namespaces.empty(); + + if (isEmpty) + it = namespaces.erase(it); + else + ++it; + } + } } \ No newline at end of file diff --git a/Src/Concerto/PackageGenerator/ClangParser/ClangParser.hpp b/Src/Concerto/PackageGenerator/ClangParser/ClangParser.hpp index d90ca5f..561b965 100644 --- a/Src/Concerto/PackageGenerator/ClangParser/ClangParser.hpp +++ b/Src/Concerto/PackageGenerator/ClangParser/ClangParser.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "Concerto/PackageGenerator/Defines.hpp" @@ -26,6 +27,8 @@ namespace cct void ProcessDeclaration(const clang::Decl* declaration); void ProcessNamespace(const clang::NamespaceDecl* namespaceDeclaration); void ProcessRecord(const clang::CXXRecordDecl* recordDeclaration); + void ProcessTemplateRecord(const clang::ClassTemplateDecl* templateDeclaration); + void ProcessTemplateSpecialization(const clang::ClassTemplateSpecializationDecl* specializationDeclaration); void ProcessEnum(const clang::EnumDecl* enumDeclaration); private: @@ -33,6 +36,8 @@ namespace cct clang::SourceManager* m_sourceManager = nullptr; const clang::LangOptions* m_langOptions = nullptr; clang::ASTContext* m_astContext = nullptr; + + void RemoveEmptyNamespaces(std::vector& namespaces); }; } diff --git a/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.cpp b/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.cpp index 8c2f13e..f987630 100644 --- a/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.cpp +++ b/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.cpp @@ -17,6 +17,7 @@ namespace cct Write("#include "); Write("#include "); Write("#include \"Concerto/Reflection/GlobalNamespace/GlobalNamespace.hpp\""); + Write("#include "); Write("#include \"{}Package.gen.hpp\"", package.name); for (auto& header : args) @@ -30,7 +31,14 @@ namespace cct for (auto& enum_ : package.enums) GenerateEnum(enum_); for (auto& klass : package.classes) - GenerateClass({}, klass); + { + if (klass.isGenericClass) + GenerateGenericClass({}, klass); + else if (klass.isTemplateClass) + GenerateTemplateClass({}, klass); + else + GenerateClass({}, klass); + } for (auto& ns : package.namepsaces) GenerateNamespace(ns); GeneratePackage(package); @@ -47,7 +55,14 @@ namespace cct for (auto& enum_ : ns.enums) GenerateEnum(enum_); for (auto& klass : ns.classes) - GenerateClass(namespaceChain + "::"s + std::string(ns.name), klass); + { + if (klass.isGenericClass) + GenerateGenericClass(namespaceChain + "::"s + std::string(ns.name), klass); + else if (klass.isTemplateClass) + GenerateTemplateClass(namespaceChain + "::"s + std::string(ns.name), klass); + else + GenerateClass(namespaceChain + "::"s + std::string(ns.name), klass); + } } LeaveScope(); Write("namespace"); @@ -89,7 +104,28 @@ namespace cct Write("ns->LoadClasses();"); LeaveScope(); for (auto& klass : ns.classes) - Write("AddClass(std::make_unique<{}::Internal{}Class>());", ns.name, klass.name); + { + if (klass.isGenericClass) + Write("AddClass(std::make_unique<{}::Internal{}GenericClass>());", ns.name, klass.name); + else if (klass.isTemplateClass) + { + Write("AddClass(std::make_unique<{}::Internal{}TemplateClass>());", ns.name, klass.name); + for (const auto& specialization : klass.templateSpecializations) + { + if (specialization.empty()) + continue; + std::string specName = specialization; + for (auto& c : specName) + { + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) + c = '_'; + } + Write("AddClass(std::make_unique<{}::Internal{}_{}_Class>());", ns.name, klass.name, specName); + } + } + else + Write("AddClass(std::make_unique<{}::Internal{}Class>());", ns.name, klass.name); + } } LeaveScope(); @@ -241,7 +277,225 @@ namespace cct } LeaveScope(";"sv); NewLine(); - Write("const cct::refl::Class* {}::m_class = nullptr;", klass.name); + } + + void CppGenerator::GenerateGenericClass(std::string_view ns, const Class& klass) + { + Write("class Internal{}GenericClass : public cct::refl::GenericClass", klass.name); + EnterScope(); + { + Write("public:"); + Write("Internal{}GenericClass() : cct::refl::GenericClass(nullptr, \"{}\"s, nullptr)", + klass.name, klass.name); + EnterScope(); + LeaveScope(); + + NewLine(); + Write("void Initialize() override"); + EnterScope(); + { + if (!ns.empty()) + Write("SetNamespace(GlobalNamespace::Get().GetNamespaceByName(\"{}\"sv));", ns); + + if (!klass.base.empty()) + { + Write("const Class* baseClass = GetClassByName(\"{}\"sv);", klass.base); + Write("CCT_ASSERT(baseClass != nullptr, \"Could not find class '{}'\");", klass.base); + Write("SetBaseClass(baseClass);"); + } + + NewLine(); + Write("SetTypeParameterCount({});", klass.genericTypeParameterFields.size()); + + for (const auto& fieldName : klass.genericTypeParameterFields) + Write("AddTypeParameter(\"{}\");", fieldName); + + NewLine(); + // Add members and methods as in regular class + for (auto& member : klass.members) + { + if (member.isNative) + Write(R"(AddNativeMemberVariable("{}", cct::TypeId<{}>());)", member.name, member.type); + else + Write(R"(AddMemberVariable("{}", cct::refl::GetClassByName("{}"sv));)", member.name, member.type); + } + + } + LeaveScope(); + + NewLine(); + Write("std::unique_ptr CreateDefaultObject() const override"); + EnterScope(); + { + Write("CCT_ASSERT_FALSE(\"Cannot instantiate generic class directly. Use CreateDefaultObject(std::span)\");"); + Write("return nullptr;"); + } + LeaveScope(); + + NewLine(); + Write("std::unique_ptr CreateDefaultObject("); + Write(" std::span typeArgs) const override"); + EnterScope(); + { + Write("if (typeArgs.size() != {})", klass.genericTypeParameterFields.size()); + EnterScope(); + { + Write("CCT_ASSERT_FALSE(\"Expected {} type arguments, got {{}}\", typeArgs.size());", + klass.genericTypeParameterFields.size()); + Write("return nullptr;"); + } + LeaveScope(); + + if (ns.empty()) + Write("auto obj = std::make_unique<{}>();", klass.name); + else + Write("auto obj = std::make_unique<{}::{}>();", ns, klass.name); + + // Inject type parameters + for (std::size_t i = 0; i < klass.genericTypeParameterFields.size(); ++i) + Write("obj->{} = typeArgs[{}];", klass.genericTypeParameterFields[i], i); + + Write("obj->SetDynamicClass(this);"); + Write("obj->InitializeMemberVariables();"); + Write("return obj;"); + } + LeaveScope(); + + NewLine(); + Write("cct::refl::Object* GetMemberVariable(std::size_t, const cct::refl::Object&) const override"); + EnterScope(); + { + Write("return nullptr;"); + } + LeaveScope(); + + NewLine(); + Write("void* GetNativeMemberVariable(std::size_t, const cct::refl::Object&) const override"); + EnterScope(); + { + Write("return nullptr;"); + } + LeaveScope(); + } + LeaveScope(";"); + } + + void CppGenerator::GenerateTemplateClass(std::string_view ns, const Class& klass) + { + Write("namespace"); + EnterScope(); + { + for (const auto& specialization : klass.templateSpecializations) + { + if (specialization.empty()) + continue; + + // Create a sanitized name for the specialization class + std::string specName = specialization; + for (auto& c : specName) + { + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) + c = '_'; + } + + Write("class Internal{}_{}_Class : public ::cct::refl::Class", klass.name, specName); + EnterScope(); + { + Write("public:"); + Write("Internal{}_{}_Class() : ::cct::refl::Class(nullptr, \"{}\"s, nullptr)", klass.name, specName, std::format("{}<{}>", klass.name, specialization)); + EnterScope(); + LeaveScope(); + NewLine(); + Write("void Initialize() override"); + EnterScope(); + { + // Specialization classes initialize their template instance + } + LeaveScope(); + NewLine(); + Write("::cct::refl::Object* GetMemberVariable(std::size_t index, const ::cct::refl::Object& self) const override"); + EnterScope(); + { + Write("return nullptr;"); + } + LeaveScope(); + NewLine(); + Write("void* GetNativeMemberVariable(std::size_t index, const ::cct::refl::Object& self) const override"); + EnterScope(); + { + Write("return nullptr;"); + } + LeaveScope(); + NewLine(); + Write("std::unique_ptr<::cct::refl::Object> CreateDefaultObject() const override"); + EnterScope(); + { + if (ns.empty()) + { + Write("auto obj = std::make_unique<{}{}>();", klass.name, std::format("<{}>", specialization)); + } + else + { + Write("auto obj = std::make_unique<{}::{}{}>();", ns, klass.name, std::format("<{}>", specialization)); + } + Write("return obj;"); + } + LeaveScope(); + } + LeaveScope(";"); + NewLine(); + } + + Write("class Internal{}TemplateClass : public ::cct::refl::TemplateClass", klass.name); + EnterScope(); + { + Write("public:"); + Write("Internal{}TemplateClass() : ::cct::refl::TemplateClass(nullptr, \"{}\"s, nullptr)", klass.name, klass.name); + EnterScope(); + LeaveScope(); + NewLine(); + Write("void Initialize() override"); + EnterScope(); + { + for (const auto& param : klass.templateParameters) + Write("cct::refl::TemplateClass::AddTemplateParameter(\"{}\");", param.name); + + if (!klass.templateSpecializations.empty()) + Write("std::vector typeArgs;"); + + for (const auto& specialization : klass.templateSpecializations) + { + if (specialization.empty()) + continue; + + // Create sanitized name + std::string specName = specialization; + for (auto& c : specName) + { + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) + c = '_'; + } + + Write("typeArgs.clear();"); + Write("typeArgs.push_back(\"{}\");", specialization); + Write("RegisterSpecialization(typeArgs, std::make_unique({}));", + klass.name, specName, ""); + } + } + LeaveScope(); + NewLine(); + Write("std::unique_ptr<::cct::refl::Object> CreateDefaultObject() const override"); + EnterScope(); + { + Write("CCT_ASSERT_FALSE(\"Cannot instantiate template class directly\");"); + Write("return nullptr;"); + } + LeaveScope(); + } + LeaveScope(";"); + NewLine(); + } + LeaveScope(); NewLine(); } @@ -252,13 +506,9 @@ namespace cct { auto it = method.tomlAttributes.as_table().find("Base"); if (it->second.is_string()) - { base = it->second.as_string(); - } else - { cct::Logger::Error("Invalid base class for method: {}::{}", className, method.name); - } } auto baseClass = base.empty() ? "cct::refl::Method"sv : base; Write("class {}{}Method : public {}", className, method.name, baseClass); diff --git a/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.hpp b/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.hpp index 3614f80..3980271 100644 --- a/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.hpp +++ b/Src/Concerto/PackageGenerator/CppGenerator/CppGenerator.hpp @@ -17,6 +17,8 @@ namespace cct private: void GenerateNamespace(const Namespace& ns, const std::string& namespaceChain = ""); void GenerateClass(std::string_view ns, const Class& klass); + void GenerateGenericClass(std::string_view ns, const Class& klass); + void GenerateTemplateClass(std::string_view ns, const Class& klass); void GenerateClassMethod(std::string_view className, const Class::Method& method, std::string_view ns, std::size_t methodIndex); void GenerateEnum(const Enum& enum_); void GeneratePackage(const Package& pkg); diff --git a/Src/Concerto/PackageGenerator/Defines.hpp b/Src/Concerto/PackageGenerator/Defines.hpp index d7dff9c..d967fd2 100644 --- a/Src/Concerto/PackageGenerator/Defines.hpp +++ b/Src/Concerto/PackageGenerator/Defines.hpp @@ -36,6 +36,13 @@ struct Enum TomlAttributes tomlAttributes; }; +struct TemplateParameter +{ + std::string name; + std::string defaultValue; + std::string constraint; +}; + struct Class { struct Member @@ -68,6 +75,15 @@ struct Class std::vector methods; std::vector members; TomlAttributes tomlAttributes; + + // Template support + std::vector templateParameters; + std::vector templateSpecializations; + bool isTemplateClass = false; + + // Generic class support + bool isGenericClass = false; + std::vector genericTypeParameterFields; }; struct Namespace diff --git a/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.cpp b/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.cpp index 3914879..e393f49 100644 --- a/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.cpp +++ b/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.cpp @@ -28,6 +28,8 @@ namespace cct Write("#include "); Write("#include "); Write("#include "); + Write("#include "); + Write("#include "); NewLine(); NewLine(); @@ -53,7 +55,12 @@ namespace cct for (auto& klass : package.classes) { - GenerateClass(klass, api); + if (klass.isGenericClass) + GenerateGenericClass(klass, api); + else if (klass.isTemplateClass) + GenerateTemplateClass(klass, api); + else + GenerateClass(klass, api); NewLine(); } @@ -112,6 +119,81 @@ namespace cct LeaveScope(";"); } + void HeaderGenerator::GenerateTemplateClass(const Class& klass, const std::string& api) + { + Write("template<", ""); + for (std::size_t i = 0; i < klass.templateParameters.size(); ++i) + { + Write("typename {}", klass.templateParameters[i].name, ""); + if (i < klass.templateParameters.size() - 1) + Write(", ", ""); + } + Write(">"); + + if (klass.base.empty()) + Write("class {} {}", api, klass.name); + else + Write("class {} {} : public {}", api, klass.name, klass.base); + EnterScope(); + { + Write("public:"); + for (const auto& member : klass.members) + { + Write("const {}& Get{}() const", member.type, Capitalize(member.name)); + EnterScope(); + Write("return _{};", member.name); + LeaveScope(); + Write("{}& Get{}()", member.type, Capitalize(member.name)); + EnterScope(); + Write("return _{};", member.name); + LeaveScope(); + } + NewLine(); + Write("CCT_OBJECT({});", klass.name); + NewLine(); + Write("private:"); + for (const auto& member : klass.members) + { + Write("{} _{};", member.type, member.name); + } + } + LeaveScope(";"); + } + + void HeaderGenerator::GenerateGenericClass(const Class& klass, const std::string& api) + { + // For generic classes, we generate the same interface as regular classes + // The actual type parameters are handled at runtime + if (klass.base.empty()) + Write("class {} {}", api, klass.name); + else + Write("class {} {} : public {}", api, klass.name, klass.base); + EnterScope(); + { + Write("public:"); + for (const auto& member : klass.members) + { + Write("const {}& Get{}() const", member.type, Capitalize(member.name)); + EnterScope(); + Write("return _{};", member.name); + LeaveScope(); + Write("{}& Get{}()", member.type, Capitalize(member.name)); + EnterScope(); + Write("return _{};", member.name); + LeaveScope(); + } + NewLine(); + Write("CCT_OBJECT({});", klass.name); + NewLine(); + Write("private:"); + for (const auto& member : klass.members) + { + Write("{} _{};", member.type, member.name); + } + } + LeaveScope(";"); + } + void HeaderGenerator::GenerateEnum(const Enum& enum_, const std::string& api) { Write("enum class {} : {};", enum_.name, enum_.base); diff --git a/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.hpp b/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.hpp index 69643b9..3014962 100644 --- a/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.hpp +++ b/Src/Concerto/PackageGenerator/HeaderGenerator/HeaderGenerator.hpp @@ -18,6 +18,8 @@ namespace cct private: void GenerateNamespace(const Namespace& ns, const std::string& api); void GenerateClass(const Class& klass, const std::string& api); + void GenerateGenericClass(const Class& klass, const std::string& api); + void GenerateTemplateClass(const Class& klass, const std::string& api); void GenerateEnum(const Enum& enum_, const std::string& api); }; } diff --git a/Src/Concerto/Reflection/Class/Class.cpp b/Src/Concerto/Reflection/Class/Class.cpp index 1759391..95e8201 100644 --- a/Src/Concerto/Reflection/Class/Class.cpp +++ b/Src/Concerto/Reflection/Class/Class.cpp @@ -247,6 +247,11 @@ namespace cct::refl return !(*this == other); } + bool Class::IsTemplateClass() const + { + return false; + } + void Class::AddMemberVariable(std::string_view name, const Class* type) { CCT_ASSERT(!GetMemberVariable(name), "Member variable already exists"); diff --git a/Src/Concerto/Reflection/Class/Class.hpp b/Src/Concerto/Reflection/Class/Class.hpp index 509abfa..7fb6c18 100644 --- a/Src/Concerto/Reflection/Class/Class.hpp +++ b/Src/Concerto/Reflection/Class/Class.hpp @@ -85,6 +85,10 @@ namespace cct::refl requires (std::is_base_of_v&& std::is_polymorphic_v) std::unique_ptr CreateDefaultObject() const; + [[nodiscard]] virtual bool IsTemplateClass() const; + + [[nodiscard]] virtual bool IsGenericClass() const { return false; } + //should be private virtual void Initialize() = 0; protected: diff --git a/Src/Concerto/Reflection/Defines.hpp b/Src/Concerto/Reflection/Defines.hpp index d132454..daceacb 100644 --- a/Src/Concerto/Reflection/Defines.hpp +++ b/Src/Concerto/Reflection/Defines.hpp @@ -22,6 +22,8 @@ #define CCT_PACKAGE(...) [[clang::annotate("cct::Package" __VA_OPT__(,) __VA_ARGS__)]] #define CCT_CLASS(...) [[clang::annotate("cct::Class" __VA_OPT__(,) __VA_ARGS__)]] +#define CCT_GENERIC_CLASS(...) [[clang::annotate("cct::GenericClass" __VA_OPT__(,) __VA_ARGS__)]] +#define CCT_GENERIC_TYPE() [[clang::annotate("cct::GenericType")]] #define CCT_MEMBER(...) [[clang::annotate("cct::Member" __VA_OPT__(,) __VA_ARGS__)]] #define CCT_NATIVE_MEMBER(...) [[clang::annotate("cct::NativeMember" __VA_OPT__(,) __VA_ARGS__)]] #define CCT_METHOD(...) [[clang::annotate("cct::Method" __VA_OPT__(,) __VA_ARGS__)]] @@ -32,6 +34,8 @@ #define CCT_PACKAGE(...) #define CCT_CLASS(...) +#define CCT_GENERIC_CLASS(...) +#define CCT_GENERIC_TYPE() #define CCT_MEMBER(...) #define CCT_NATIVE_MEMBER(...) #define CCT_METHOD(...) diff --git a/Src/Concerto/Reflection/GenericClass/GenericClass.cpp b/Src/Concerto/Reflection/GenericClass/GenericClass.cpp new file mode 100644 index 0000000..3dbe47d --- /dev/null +++ b/Src/Concerto/Reflection/GenericClass/GenericClass.cpp @@ -0,0 +1,74 @@ +// +// Created by arthur on 16/12/2025 +// + +#include "Concerto/Reflection/GenericClass/GenericClass.hpp" + +namespace cct::refl +{ + GenericClass::GenericClass(Namespace* nameSpace, std::string name, const Class* baseClass) + : Class(nameSpace, std::move(name), baseClass), + m_typeParameterCount(0) + { + } + + std::size_t GenericClass::GetTypeParameterCount() const + { + return m_typeParameterCount; + } + + std::span GenericClass::GetTypeParameterNames() const + { + return m_typeParameterNames; + } + + std::unique_ptr GenericClass::CreateDefaultObject() const + { + CCT_ASSERT_FALSE("Cannot instantiate generic class template directly. Use CreateDefaultObject(std::span)"); + return nullptr; + } + + std::unique_ptr GenericClass::CreateDefaultObject( + std::span typeArgs) const + { + if (typeArgs.size() != m_typeParameterCount) + { + CCT_ASSERT_FALSE("Type argument count mismatch for '{}': expected {}, got {}", + GetName(), m_typeParameterCount, typeArgs.size()); + return nullptr; + } + + for (std::size_t i = 0; i < typeArgs.size(); ++i) + { + if (!typeArgs[i]) + { + CCT_ASSERT_FALSE("Type argument {} for '{}' is nullptr", i, GetName()); + return nullptr; + } + } + + CCT_ASSERT_FALSE("GenericClass::CreateDefaultObject(span) not implemented. This should be overridden in generated code."); + return nullptr; + } + + bool GenericClass::IsGenericClass() const + { + return true; + } + + void GenericClass::AddTypeParameter(std::string fieldName) + { + m_typeParameterNames.push_back(std::move(fieldName)); + } + + void GenericClass::SetTypeParameterCount(std::size_t count) + { + m_typeParameterCount = count; + } + + void GenericClass::Initialize() + { + // Base implementation does nothing + // Subclasses override this to set up type parameters and other metadata + } +} diff --git a/Src/Concerto/Reflection/GenericClass/GenericClass.hpp b/Src/Concerto/Reflection/GenericClass/GenericClass.hpp new file mode 100644 index 0000000..d9a56d4 --- /dev/null +++ b/Src/Concerto/Reflection/GenericClass/GenericClass.hpp @@ -0,0 +1,53 @@ +// +// Created by arthur on 16/12/2025 +// + +#ifndef CONCERTO_REFLECTION_GENERIC_CLASS_HPP +#define CONCERTO_REFLECTION_GENERIC_CLASS_HPP + +#include +#include +#include +#include +#include + +#include "Concerto/Reflection/Class/Class.hpp" + +namespace cct::refl +{ + class CCT_REFLECTION_API GenericClass : public Class + { + public: + GenericClass(Namespace* nameSpace, std::string name, const Class* baseClass); + ~GenericClass() override = default; + + GenericClass(const GenericClass&) = delete; + GenericClass(GenericClass&&) = default; + + GenericClass& operator=(const GenericClass&) = delete; + GenericClass& operator=(GenericClass&&) = default; + + [[nodiscard]] std::size_t GetTypeParameterCount() const; + + [[nodiscard]] std::span GetTypeParameterNames() const; + + [[nodiscard]] std::unique_ptr CreateDefaultObject() const override; + + [[nodiscard]] virtual std::unique_ptr CreateDefaultObject(std::span typeArgs) const; + + [[nodiscard]] bool IsGenericClass() const override; + + protected: + void AddTypeParameter(std::string fieldName); + + void SetTypeParameterCount(std::size_t count); + + private: + virtual void Initialize() override; + + std::vector m_typeParameterNames; + std::size_t m_typeParameterCount; + }; +} + +#endif //CONCERTO_REFLECTION_GENERIC_CLASS_HPP diff --git a/Src/Concerto/Reflection/Object/Object.refl.hpp b/Src/Concerto/Reflection/Object/Object.refl.hpp index d3147c6..a41dba3 100644 --- a/Src/Concerto/Reflection/Object/Object.refl.hpp +++ b/Src/Concerto/Reflection/Object/Object.refl.hpp @@ -12,12 +12,12 @@ #include "Concerto/Reflection/Defines.hpp" -#define CCT_OBJECT(className) \ - public: \ - static const cct::refl::Class* GetClass() \ - {return m_class;} \ - private: \ - static const cct::refl::Class* m_class; \ +#define CCT_OBJECT(className) \ + public: \ + static const cct::refl::Class* GetClass() \ + {return m_class;} \ + private: \ + inline static const cct::refl::Class* m_class; \ friend class Internal##className##Class struct CCT_REFL_PACKAGE("version = \"1.0.0\"", "description = \"Concerto Reflection Standard Package\"") ConcertoReflection {}; diff --git a/Src/Concerto/Reflection/TemplateClass/TemplateClass.cpp b/Src/Concerto/Reflection/TemplateClass/TemplateClass.cpp new file mode 100644 index 0000000..9938f43 --- /dev/null +++ b/Src/Concerto/Reflection/TemplateClass/TemplateClass.cpp @@ -0,0 +1,97 @@ +// +// Created by arthur on 10/12/2024. +// + +#include "Concerto/Reflection/TemplateClass/TemplateClass.hpp" +#include "Concerto/Reflection/Namespace/Namespace.hpp" + +namespace cct::refl +{ + TemplateClass::TemplateClass(Namespace* nameSpace, std::string name, const Class* baseClass) + : Class(nameSpace, std::move(name), baseClass) + { + } + + std::span TemplateClass::GetTemplateParameters() const + { + return m_templateParameters; + } + + const Class* TemplateClass::GetSpecialization(std::span typeArgs) const + { + const auto key = MakeSpecializationKey(typeArgs); + const auto it = m_specializations.find(key); + if (it != m_specializations.end()) + { + return it->second; + } + return nullptr; + } + + const Class* TemplateClass::GetSpecialization(std::string_view typeArg) const + { + std::vector typeArgs; + typeArgs.push_back(typeArg); + return GetSpecialization(std::span(typeArgs)); + } + + bool TemplateClass::HasSpecialization(std::span typeArgs) const + { + return GetSpecialization(typeArgs) != nullptr; + } + + bool TemplateClass::IsTemplateClass() const + { + return true; + } + + std::unique_ptr TemplateClass::CreateDefaultObject() const + { + // Template classes cannot be instantiated directly + // Must use a specialization + return nullptr; + } + + void TemplateClass::AddTemplateParameter(std::string name) + { + m_templateParameters.push_back(std::move(name)); + } + + void TemplateClass::RegisterSpecialization(std::vector typeArgs, std::unique_ptr specializedClass) + { + std::vector typeArgsView(typeArgs.begin(), typeArgs.end()); + const auto key = MakeSpecializationKey(typeArgsView); + m_specializations[key] = specializedClass.release(); + } + + std::string TemplateClass::MakeSpecializationKey(std::span typeArgs) const + { + std::string key; + for (std::size_t i = 0; i < typeArgs.size(); ++i) + { + key += typeArgs[i]; + if (i < typeArgs.size() - 1) + { + key += SpecializationSeparator; + } + } + return key; + } + + cct::refl::Object* TemplateClass::GetMemberVariable(std::size_t index, const cct::refl::Object& self) const + { + // Template classes don't have member access + return nullptr; + } + + void* TemplateClass::GetNativeMemberVariable(std::size_t index, const cct::refl::Object& self) const + { + // Template classes don't have native member access + return nullptr; + } + + void TemplateClass::Initialize() + { + // Initialize template class + } +} diff --git a/Src/Concerto/Reflection/TemplateClass/TemplateClass.hpp b/Src/Concerto/Reflection/TemplateClass/TemplateClass.hpp new file mode 100644 index 0000000..f1163f0 --- /dev/null +++ b/Src/Concerto/Reflection/TemplateClass/TemplateClass.hpp @@ -0,0 +1,59 @@ +// +// Created by arthur on 10/12/2024. +// + +#ifndef CONCERTO_REFLECTION_TEMPLATE_CLASS_HPP +#define CONCERTO_REFLECTION_TEMPLATE_CLASS_HPP + +#include +#include +#include +#include +#include +#include + +#include "Concerto/Reflection/Class/Class.hpp" + +namespace cct::refl +{ + class CCT_REFLECTION_API TemplateClass : public Class + { + public: + TemplateClass(Namespace* nameSpace, std::string name, const Class* baseClass); + ~TemplateClass() override = default; + + TemplateClass(const TemplateClass&) = delete; + TemplateClass(TemplateClass&&) = default; + + TemplateClass& operator=(const TemplateClass&) = delete; + TemplateClass& operator=(TemplateClass&&) = default; + + [[nodiscard]] std::span GetTemplateParameters() const; + [[nodiscard]] const Class* GetSpecialization(std::span typeArgs) const; + [[nodiscard]] const Class* GetSpecialization(std::string_view typeArg) const; + [[nodiscard]] bool HasSpecialization(std::span typeArgs) const; + + [[nodiscard]] bool IsTemplateClass() const override; + + virtual std::unique_ptr CreateDefaultObject() const override; + + protected: + void AddTemplateParameter(std::string name); + void RegisterSpecialization(std::vector typeArgs, std::unique_ptr specializedClass); + + private: + [[nodiscard]] cct::refl::Object* GetMemberVariable(std::size_t index, const cct::refl::Object& self) const override; + [[nodiscard]] void* GetNativeMemberVariable(std::size_t index, const cct::refl::Object& self) const override; + virtual void Initialize() override; + + std::vector m_templateParameters; + std::unordered_map m_specializations; + + static constexpr std::string_view SpecializationSeparator = ","; + std::string MakeSpecializationKey(std::span typeArgs) const; + }; +} + +#include "Concerto/Reflection/TemplateClass/TemplateClass.inl" + +#endif //CONCERTO_REFLECTION_TEMPLATE_CLASS_HPP diff --git a/Src/Concerto/Reflection/TemplateClass/TemplateClass.inl b/Src/Concerto/Reflection/TemplateClass/TemplateClass.inl new file mode 100644 index 0000000..55ef807 --- /dev/null +++ b/Src/Concerto/Reflection/TemplateClass/TemplateClass.inl @@ -0,0 +1,12 @@ +// +// Created by arthur on 10/12/2024. +// + +#ifndef CONCERTO_REFLECTION_TEMPLATE_CLASS_INL +#define CONCERTO_REFLECTION_TEMPLATE_CLASS_INL + +namespace cct::refl +{ +} + +#endif //CONCERTO_REFLECTION_TEMPLATE_CLASS_INL diff --git a/Src/Tests/Class.cpp b/Src/Tests/Class.cpp index 94b91de..013322e 100644 --- a/Src/Tests/Class.cpp +++ b/Src/Tests/Class.cpp @@ -22,7 +22,7 @@ SCENARIO("Class metadata verification") REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); packageLoader.LoadPackages(); - CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 7); + CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 11); CHECK(cct::refl::GlobalNamespace::Get().GetNamespaceCount() == 1); THEN("We are getting the class Object") diff --git a/Src/Tests/Defines.hpp b/Src/Tests/Defines.hpp index 7088cd6..dc1f54c 100644 --- a/Src/Tests/Defines.hpp +++ b/Src/Tests/Defines.hpp @@ -10,6 +10,7 @@ #define CCT_REFL_TESTS_PACKAGE(...) CCT_PACKAGE(__VA_ARGS__) #define CCT_REFL_TESTS_CLASS(...) CCT_CLASS(__VA_ARGS__) +#define CCT_REFL_TESTS_GENERIC_TYPE() CCT_GENERIC_TYPE() #define CCT_REFL_TESTS_MEMBER(...) CCT_MEMBER(__VA_ARGS__) #define CCT_REFL_TESTS_NATIVE_MEMBER(...) CCT_NATIVE_MEMBER(__VA_ARGS__) #define CCT_REFL_TESTS_METHOD(...) CCT_METHOD(__VA_ARGS__) @@ -20,6 +21,7 @@ #define CCT_REFL_TESTS_PACKAGE(...) #define CCT_REFL_TESTS_CLASS(...) +#define CCT_REFL_TESTS_GENERIC_TYPE(name) #define CCT_REFL_TESTS_MEMBER(...) #define CCT_REFL_TESTS_NATIVE_MEMBER(...) #define CCT_REFL_TESTS_METHOD(...) diff --git a/Src/Tests/GenericClass.cpp b/Src/Tests/GenericClass.cpp new file mode 100644 index 0000000..4645fb5 --- /dev/null +++ b/Src/Tests/GenericClass.cpp @@ -0,0 +1,106 @@ +// +// Created by arthur on 12/16/2025. +// +#include + +#include +#include + +#include +#include +#include +#include + +SCENARIO("GenericClass identification") +{ + using namespace std::string_view_literals; + GIVEN("Multiple class types in the system") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Checking template vs generic vs regular classes") + { + THEN("Regular classes are neither template nor generic") + { + const auto* objectClass = cct::refl::GetClassByName("cct::refl::Object"); + REQUIRE(objectClass != nullptr); + CHECK(objectClass->IsTemplateClass() == false); + CHECK(objectClass->IsGenericClass() == false); + + const auto* sampleBarClass = cct::refl::GetClassByName("cct::sample::SampleBar"); + REQUIRE(sampleBarClass != nullptr); + CHECK(sampleBarClass->IsTemplateClass() == false); + CHECK(sampleBarClass->IsGenericClass() == false); + } + } + } +} + +SCENARIO("GenericPair multi-parameter generic class") +{ + using namespace std::string_view_literals; + GIVEN("GenericPair class loaded") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Looking up GenericPair class") + { + const auto* genericPairClass = cct::refl::GetClassByName("cct::sample::GenericPair"); + + THEN("GenericPair is found and identified as generic") + { + REQUIRE(genericPairClass != nullptr); + CHECK(genericPairClass->IsGenericClass() == true); + } + + THEN("GenericPair has two type parameters") + { + REQUIRE(genericPairClass != nullptr); + const auto* genericClass = dynamic_cast(genericPairClass); + REQUIRE(genericClass != nullptr); + CHECK(genericClass->GetTypeParameterCount() == 2); + } + + THEN("GenericPair type parameters are m_keyType and m_valueType") + { + REQUIRE(genericPairClass != nullptr); + const auto* genericClass = dynamic_cast(genericPairClass); + REQUIRE(genericClass != nullptr); + + auto paramNames = genericClass->GetTypeParameterNames(); + REQUIRE(paramNames.size() == 2); + CHECK(paramNames[0] == "m_keyType"sv); + CHECK(paramNames[1] == "m_valueType"sv); + } + + THEN("GenericPair can be instantiated with two type arguments") + { + REQUIRE(genericPairClass != nullptr); + const auto* genericClass = dynamic_cast(genericPairClass); + REQUIRE(genericClass != nullptr); + + const auto* int32Class = cct::refl::GetClassByName("cct::refl::Int32"); + const auto* int64Class = cct::refl::GetClassByName("cct::refl::Int64"); + REQUIRE(int32Class != nullptr); + REQUIRE(int64Class != nullptr); + + std::vector typeArgs = { int32Class, int64Class }; + auto obj = genericClass->CreateDefaultObject( + std::span(typeArgs)); + REQUIRE(obj != nullptr); + CHECK(obj->GetDynamicClass() == genericPairClass); + + auto genericPair = dynamic_cast(obj.get()); + REQUIRE(genericPair != nullptr); + CHECK(genericPair->GetKeyType() == int32Class); + CHECK(genericPair->GetValueType() == int64Class); + } + } + } +} diff --git a/Src/Tests/Method.cpp b/Src/Tests/Method.cpp index 34f1d89..889228a 100644 --- a/Src/Tests/Method.cpp +++ b/Src/Tests/Method.cpp @@ -23,7 +23,7 @@ SCENARIO("Method") REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); packageLoader.LoadPackages(); - CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 7); + CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 11); CHECK(cct::refl::GlobalNamespace::Get().GetNamespaceCount() == 1); THEN("We are invoking a method named 'Bar'") diff --git a/Src/Tests/Namespace.cpp b/Src/Tests/Namespace.cpp index 90489cc..0dde6eb 100644 --- a/Src/Tests/Namespace.cpp +++ b/Src/Tests/Namespace.cpp @@ -21,7 +21,7 @@ SCENARIO("Namespace") REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); packageLoader.LoadPackages(); - CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 7); + CHECK(cct::refl::GlobalNamespace::Get().GetClassCount() == 11); CHECK(cct::refl::GlobalNamespace::Get().GetNamespaceCount() == 1); THEN("We are getting the cct namespace") diff --git a/Src/Tests/SampleBar.cpp b/Src/Tests/SampleBar.cpp index 4d6f536..f07e121 100644 --- a/Src/Tests/SampleBar.cpp +++ b/Src/Tests/SampleBar.cpp @@ -1,2 +1 @@ #include "SampleBar.refl.hpp" - diff --git a/Src/Tests/SampleBar.refl.hpp b/Src/Tests/SampleBar.refl.hpp index 3ab1846..350e6b5 100644 --- a/Src/Tests/SampleBar.refl.hpp +++ b/Src/Tests/SampleBar.refl.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -54,6 +55,77 @@ namespace cct::sample { return refl::Int32(); }; - + + }; + + // Templated class with actual generic members + template + class CCT_REFL_TESTS_CLASS() TemplatedPair : public cct::refl::Object + { + public: + TemplatedPair() : m_key(), m_value() {} + + CCT_REFL_TESTS_NATIVE_MEMBER() + K m_key; + + CCT_REFL_TESTS_NATIVE_MEMBER() + V m_value; + + CCT_REFL_TESTS_METHOD() + void Set(K k, V v) + { + m_key = k; + m_value = v; + } + + CCT_REFL_TESTS_METHOD() + K GetKey() const + { + return m_key; + } + + CCT_REFL_TESTS_METHOD() + V GetValue() const + { + return m_value; + } + + CCT_OBJECT(TemplatedPair); + }; + + template class TemplatedPair; + template class TemplatedPair; + + // Generic class with multiple type parameters (runtime parameterized) + class CCT_REFL_TESTS_CLASS() CCT_GENERIC_CLASS() GenericPair : public cct::refl::Object + { + public: + GenericPair() : + m_keyType(nullptr), + m_valueType(nullptr) + { + } + + CCT_REFL_TESTS_MEMBER() + CCT_REFL_TESTS_GENERIC_TYPE() + const cct::refl::Class* m_keyType; + + CCT_REFL_TESTS_MEMBER() + CCT_REFL_TESTS_GENERIC_TYPE() + const cct::refl::Class* m_valueType; + + CCT_REFL_TESTS_METHOD() + const cct::refl::Class* GetKeyType() const + { + return m_keyType; + } + + CCT_REFL_TESTS_METHOD() + const cct::refl::Class* GetValueType() const + { + return m_valueType; + } + + CCT_OBJECT(GenericPair); }; } diff --git a/Src/Tests/TemplateClass.cpp b/Src/Tests/TemplateClass.cpp new file mode 100644 index 0000000..c317a06 --- /dev/null +++ b/Src/Tests/TemplateClass.cpp @@ -0,0 +1,180 @@ +// +// Created by arthur on 10/12/2024. +// +#define CATCH_CONFIG_RUNNER +#include + +#include +#include + +#include +#include +#include + +SCENARIO("TemplateClass API with loaded packages") +{ + using namespace std::string_view_literals; + GIVEN("Loaded reflection packages with template support") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Checking class IsTemplateClass property") + { + THEN("Non-template classes return false") + { + const auto* sampleBarClass = cct::refl::GetClassByName("cct::sample::SampleBar"); + REQUIRE(sampleBarClass != nullptr); + CHECK(sampleBarClass->IsTemplateClass() == false); + } + + THEN("Template specializations are identified as templates") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr) + { + bool isTemplate = templatedPairClass->IsTemplateClass(); + CHECK(isTemplate == true); + } + } + } + + WHEN("Accessing template specializations") + { + THEN("Specializations are retrievable") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr && templatedPairClass->IsTemplateClass()) + { + const auto* templateClass = dynamic_cast(templatedPairClass); + REQUIRE(templateClass != nullptr); + + CHECK(templatedPairClass->GetName() == "TemplatedPair"); + } + } + } + } +} + +SCENARIO("TemplateClass identification") +{ + using namespace std::string_view_literals; + GIVEN("Multiple class types in the system") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Checking template vs non-template classes") + { + THEN("Built-in classes are not templates") + { + const auto* objectClass = cct::refl::GetClassByName("cct::refl::Object"); + REQUIRE(objectClass != nullptr); + CHECK(objectClass->IsTemplateClass() == false); + + const auto* int32Class = cct::refl::GetClassByName("cct::refl::Int32"); + REQUIRE(int32Class != nullptr); + CHECK(int32Class->IsTemplateClass() == false); + } + + THEN("User classes are not templates") + { + const auto* sampleBarClass = cct::refl::GetClassByName("cct::sample::SampleBar"); + REQUIRE(sampleBarClass != nullptr); + CHECK(sampleBarClass->IsTemplateClass() == false); + } + + THEN("Template specializations are templates") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr) + { + CHECK(templatedPairClass->IsTemplateClass() == true); + } + } + } + } +} + +SCENARIO("TemplateClass specialization retrieval by string") +{ + using namespace std::string_view_literals; + GIVEN("Template class with specializations loaded") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Getting specializations by template arguments") + { + THEN("Can retrieve TemplatedPair specialization") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr && templatedPairClass->IsTemplateClass()) + { + const auto* templateClass = dynamic_cast(templatedPairClass); + REQUIRE(templateClass); + CHECK(templatedPairClass->GetName() == "TemplatedPair"); + } + } + + THEN("Can retrieve TemplatedPair specialization") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr && templatedPairClass->IsTemplateClass()) + { + const auto* templateClass = dynamic_cast(templatedPairClass); + REQUIRE(templateClass); + CHECK(templatedPairClass->GetName() == "TemplatedPair"); + } + } + } + + WHEN("Accessing specialized class methods") + { + THEN("Specialization classes have initialized structure") + { + const auto* templatedPairClass = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + if (templatedPairClass != nullptr && templatedPairClass->IsTemplateClass()) + { + const auto* templateClass = dynamic_cast(templatedPairClass); + REQUIRE(templateClass); + CHECK(templatedPairClass->GetMethodCount() > 0); + CHECK(templatedPairClass->GetMemberVariableCount() > 0); + } + } + } + } +} + +SCENARIO("TemplateClass retrieval by GetClassByName") +{ + using namespace std::string_view_literals; + GIVEN("Loaded reflection packages with template specializations") + { + cct::refl::PackageLoader packageLoader; + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionPackage())); + REQUIRE(packageLoader.AddPackage(CreateConcertoReflectionTestsPackage())); + packageLoader.LoadPackages(); + + WHEN("Getting template specializations by name") + { + THEN("TemplatedPair specializations may or may not be in reflection") + { + const auto* notExistsSpec = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + REQUIRE(notExistsSpec == nullptr); + } + + THEN("Non-existent template specialization returns nullptr") + { + const auto* invalid = cct::refl::GetClassByName("cct::sample::TemplatedPair"); + CHECK(invalid == nullptr); + } + } + } +} diff --git a/Xmake/rules/cct_cpp_reflect.lua b/Xmake/rules/cct_cpp_reflect.lua index 491bc99..cddbc0b 100644 --- a/Xmake/rules/cct_cpp_reflect.lua +++ b/Xmake/rules/cct_cpp_reflect.lua @@ -10,6 +10,12 @@ rule("cct_cpp_reflect") target:add("files", generatedCpp, {always_added = true}) target:add("includedirs", target:autogendir(), {public = true}) target:add("defines", path.basename(targetName):upper() .. "_BUILD", { public = false }) + + local upperPackageName = path.basename(targetName):upper() + target:add("defines", upperPackageName .. "PACKAGE_BUILD", { public = false }) + if target:kind() == "static" then + target:add("defines", upperPackageName .. "PACKAGE_STATIC", { public = true }) + end end) before_buildcmd_files(function (target, batchcmds, sourcebatch, opt) @@ -105,4 +111,4 @@ rule("cct_cpp_reflect") batchcmds:add_depfiles(sourcebatch.sourcefiles) batchcmds:set_depmtime(os.mtime(outputCppFile)) batchcmds:set_depcache(target:dependfile(outputCppFile)) - end) \ No newline at end of file + end) diff --git a/xmake.lua b/xmake.lua index f255857..f27d92f 100644 --- a/xmake.lua +++ b/xmake.lua @@ -123,6 +123,7 @@ target("concerto-reflection") local files = { ".", "Class", + "GenericClass", "GlobalNamespace", "MemberVariable", "Method", @@ -131,6 +132,8 @@ target("concerto-reflection") "Package", "PackageLoader", "Registry", + "TemplateClass", + "Signal", } for _, dir in ipairs(files) do add_files_to_target("Src/Concerto/Reflection/" .. dir, true)