From ffe33a2c61526af2be75a8677affc35cf6c7e517 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Mon, 27 Jan 2025 16:03:23 +0100 Subject: [PATCH] App: Add compile time checks for type system class name Ensures: * _class_ is based on BaseClass * _class_ parameter includes a namespace And when _class_ is a document object * namespace is not global namespace * namespace name is in path after /src/ or /src/Mod/ The document object requirements are needed to auto import modules when using addObject --- src/App/PropertyContainer.h | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/App/PropertyContainer.h b/src/App/PropertyContainer.h index 819e4d8c1f..5a6b7647ae 100644 --- a/src/App/PropertyContainer.h +++ b/src/App/PropertyContainer.h @@ -305,6 +305,49 @@ private: \ /// Like PROPERTY_HEADER, but with overridden methods declared as such #define PROPERTY_HEADER_WITH_OVERRIDE(_class_) \ TYPESYSTEM_HEADER_WITH_OVERRIDE(); \ +public: \ + static constexpr const char* getClassName() {\ + /* + TODO: When c++20 is available \ + - Use consteval to ensure the function is evaluated at compile time \ + - Move bulk of the function to a template `validate()` \ + - Use `starts_with` when checking namespace in path \ + */ \ + \ + static_assert(sizeof(_class_) > 0, "Class is not complete"); \ + \ + constexpr const char* sClass = #_class_; \ + constexpr std::string_view vClass {sClass}; \ + static_assert(vClass[0] != '\0', "Class name must not be empty"); \ + static_assert(std::is_base_of::value, \ + "Class must be derived from App::PropertyContainer"); \ + \ + constexpr bool isSubClassOfDocObj = std::is_base_of::value && \ + !std::is_same::value; \ + if constexpr (isSubClassOfDocObj) { \ + constexpr auto pos = vClass.find("::"); \ + static_assert(pos != std::string_view::npos, \ + "Class name must be fully qualified for document object derived classes"); \ + static_assert(pos != 0, "Namespace must not be empty"); \ + \ + constexpr auto vNamespace = vClass.substr(0, pos); \ + constexpr std::string_view filePath = __FILE__; \ + constexpr auto posAfterSrcMod = filePath.find("/src/Mod/"); \ + if constexpr (constexpr bool hasSrcModInPath = posAfterSrcMod != std::string_view::npos) { \ + constexpr auto pathAfterSrcMod = filePath.substr(posAfterSrcMod + 9); \ + /* some workarounds are needed, if CI is ok in the future, remove these: \ + - isSubClassOfDocObj shouldn't be needed, but it is for some compilers \ + - allowing `Path` until it's been properly renamed */ \ + constexpr bool workarounds = !isSubClassOfDocObj || vNamespace == "Path"; \ + /* TODO: use `starts_with` instead of `find` when c++20 is available */ \ + constexpr bool isPathOk = pathAfterSrcMod.find(vNamespace) != std::string_view::npos; \ + static_assert(workarounds || isPathOk, \ + "Classes in `src/Mod` needs to be in a directory with the same name as" \ + " the namespace in order to load correctly"); \ + } \ + } \ + return sClass; \ + } \ protected: \ static const App::PropertyData * getPropertyDataPtr(void); \ const App::PropertyData &getPropertyData(void) const override; \