diff --git a/.gitmodules b/.gitmodules index f43f95f..21ac3b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ThirdParty/fmt"] path = ThirdParty/fmt url = https://github.com/fmtlib/fmt +[submodule "Tests/Unit/test_headers/ThirdParty/glm"] + path = Tests/Unit/test_headers/ThirdParty/glm + url = https://github.com/g-truc/glm diff --git a/LLVM/include/RG3/LLVM/Utils.h b/LLVM/include/RG3/LLVM/Utils.h index 332b1fd..cff1cc1 100644 --- a/LLVM/include/RG3/LLVM/Utils.h +++ b/LLVM/include/RG3/LLVM/Utils.h @@ -11,6 +11,15 @@ namespace rg3::llvm { + struct TypeBaseInfo + { + cpp::TypeKind eKind; + std::string sName; + std::string sPrettyName; + cpp::CppNamespace sNameSpace; + cpp::DefinitionLocation sDefLocation; + }; + struct Utils { static void getDeclInfo(const clang::Decl* decl, rg3::cpp::CppNamespace& nameSpace); @@ -19,7 +28,7 @@ namespace rg3::llvm static cpp::ClassEntryVisibility getDeclVisibilityLevel(const clang::Decl* decl); - static std::string getNormalizedTypeRef(const std::string& typeName); + static bool getQualTypeBaseInfo(const clang::QualType& qualType, TypeBaseInfo& baseInfo, const clang::ASTContext& astContext); static void fillTypeStatementFromQualType(rg3::cpp::TypeStatement& typeStatement, clang::QualType qt, const clang::ASTContext& astContext); diff --git a/LLVM/source/Utils.cpp b/LLVM/source/Utils.cpp index a352dbd..c98e8b6 100644 --- a/LLVM/source/Utils.cpp +++ b/LLVM/source/Utils.cpp @@ -1,10 +1,14 @@ #include +#include +#include +#include #include #include #include #include #include #include +#include namespace rg3::llvm @@ -84,34 +88,173 @@ namespace rg3::llvm return cpp::ClassEntryVisibility::CEV_PRIVATE; } - std::string Utils::getNormalizedTypeRef(const std::string& typeName) + bool Utils::getQualTypeBaseInfo(const clang::QualType& qualType, TypeBaseInfo& baseInfo, const clang::ASTContext& astContext) { - // Here stored known cases when we need to replace one type to another - static const std::unordered_map s_Replacement { - { "_Bool", "bool" } - }; + if (qualType->isTypedefNameType()) // must be first! + { + // Alias. Just use alias name & location data + if (auto* pAsAliasType = qualType->getAs()) + { + if (auto* pAsAliasDecl = pAsAliasType->getDecl()) + { + // Detect name + clang::PrintingPolicy typeNamePrintingPolicy { astContext.getLangOpts() }; + typeNamePrintingPolicy.SuppressTagKeyword = true; + typeNamePrintingPolicy.SuppressScope = false; + typeNamePrintingPolicy.Bool = true; + + // Detect namespace + Utils::getDeclInfo(pAsAliasDecl, baseInfo.sNameSpace); + + // Collect definition location + baseInfo.sDefLocation = Utils::getDeclDefinitionInfo(pAsAliasDecl); + + // Store info + baseInfo.sName = qualType.getAsString(typeNamePrintingPolicy); + baseInfo.sPrettyName = baseInfo.sNameSpace.isEmpty() ? baseInfo.sName : fmt::format("{}::{}", baseInfo.sNameSpace.asString(), baseInfo.sName); + + // Detect kind + if (qualType->isRecordType()) + { + baseInfo.eKind = cpp::TypeKind::TK_STRUCT_OR_CLASS; + } + else if (qualType->isEnumeralType() || qualType->isScopedEnumeralType()) + { + baseInfo.eKind = cpp::TypeKind::TK_ENUM; + } + else + { + baseInfo.eKind = cpp::TypeKind::TK_TRIVIAL; + } + + return true; + } + } - if (auto it = s_Replacement.find(typeName); it != s_Replacement.end()) + return false; + } + + if (qualType->isRecordType()) { - return it->second; + // No need to support full type analysis pipeline here. Just lookup as 'generic record' and trying to extract type + if (auto* pAsRecord = qualType->getAsRecordDecl()) + { + // Collect valid name + clang::PrintingPolicy typeNamePrintingPolicy { astContext.getLangOpts() }; + typeNamePrintingPolicy.SuppressTagKeyword = true; + typeNamePrintingPolicy.SuppressScope = true; + typeNamePrintingPolicy.Bool = true; + + const auto correctedName = qualType.getUnqualifiedType().getAsString(typeNamePrintingPolicy); + + // Collect namespace + Utils::getDeclInfo(pAsRecord, baseInfo.sNameSpace); + + // Collect definition location + cpp::DefinitionLocation aDefLocation = Utils::getDeclDefinitionInfo(pAsRecord); + + // Save info + baseInfo.sName = correctedName; + baseInfo.sNameSpace = baseInfo.sNameSpace; + baseInfo.sPrettyName = baseInfo.sNameSpace.isEmpty() ? correctedName : fmt::format("{}::{}", baseInfo.sNameSpace.asString(), correctedName); + baseInfo.sDefLocation = aDefLocation; + return true; + } + + return false; } - return typeName; + if (qualType->isEnumeralType() || qualType->isScopedEnumeralType()) + { + // C/C++ enum + if (auto* pAsEnumType = qualType->getAs()) + { + if (auto* pAsEnumDecl = pAsEnumType->getDecl()) + { + CompilerConfig cc {}; + cc.bAllowCollectNonRuntimeTypes = true; + + std::vector vCollected {}; + + visitors::CxxTypeVisitor visitor { vCollected, cc }; + visitor.TraverseDecl(pAsEnumDecl); + + if (!vCollected.empty() && vCollected[0]->getKind() == cpp::TypeKind::TK_ENUM && !vCollected[0]->getPrettyName().empty()) + { + baseInfo.eKind = cpp::TypeKind::TK_ENUM; + baseInfo.sName = vCollected[0]->getName(); + baseInfo.sNameSpace = vCollected[0]->getNamespace(); + baseInfo.sPrettyName = vCollected[0]->getPrettyName(); + baseInfo.sDefLocation = vCollected[0]->getDefinition(); + + return true; + } + } + } + + return false; + } + + if (qualType->isBuiltinType()) + { + // Builtin type (int, float, etc...). Namespace not supported here + if (auto* pAsBuiltinType = qualType->getAs()) + { + // It's builtin, just register a type + // Our builtins are store as generic rg3::cpp::TypeBase instances + clang::PrintingPolicy typeNamePrintingPolicy { astContext.getLangOpts() }; + typeNamePrintingPolicy.SuppressTagKeyword = true; + typeNamePrintingPolicy.SuppressScope = false; + typeNamePrintingPolicy.Bool = true; + + baseInfo.eKind = cpp::TypeKind::TK_TRIVIAL; + baseInfo.sName = baseInfo.sPrettyName = pAsBuiltinType->getNameAsCString(typeNamePrintingPolicy); + baseInfo.sNameSpace = {}; + baseInfo.sDefLocation = {}; + return true; + } + } + + // Not supported yet + return false; } void Utils::fillTypeStatementFromQualType(rg3::cpp::TypeStatement& typeStatement, clang::QualType qt, const clang::ASTContext& astContext) { const clang::SourceManager& sm = astContext.getSourceManager(); - typeStatement.sTypeRef = cpp::TypeReference(rg3::llvm::Utils::getNormalizedTypeRef(qt.getAsString())); + { + TypeBaseInfo typeBaseInfo {}; + if (!getQualTypeBaseInfo(qt, typeBaseInfo, astContext)) + { + // Use "pure" view + typeStatement.sTypeRef = cpp::TypeReference(qt.getUnqualifiedType().getAsString()); + } + else + { + // Use correct view + typeStatement.sTypeRef = cpp::TypeReference(typeBaseInfo.sPrettyName); + } + } typeStatement.bIsConst = qt.isConstQualified(); if (qt->isPointerType() || qt->isReferenceType()) { + TypeBaseInfo typeBaseInfo {}; + if (!getQualTypeBaseInfo(qt->getPointeeType().getUnqualifiedType(), typeBaseInfo, astContext)) + { + // Use "pure" view + typeStatement.sTypeRef = cpp::TypeReference(qt->getPointeeType().getUnqualifiedType().getAsString()); + } + else + { + // Use correct view + typeStatement.sTypeRef = cpp::TypeReference(typeBaseInfo.sPrettyName); + } + typeStatement.bIsPointer = qt->isPointerType(); typeStatement.bIsReference = qt->isReferenceType(); - typeStatement.sTypeRef = cpp::TypeReference(rg3::llvm::Utils::getNormalizedTypeRef(qt->getPointeeType().getUnqualifiedType().getAsString())); typeStatement.bIsPtrConst = qt->getPointeeType().isConstQualified(); } diff --git a/LLVM/source/Visitors/CxxClassTypeVisitor.cpp b/LLVM/source/Visitors/CxxClassTypeVisitor.cpp index 110d95d..a0a2e1d 100644 --- a/LLVM/source/Visitors/CxxClassTypeVisitor.cpp +++ b/LLVM/source/Visitors/CxxClassTypeVisitor.cpp @@ -108,7 +108,7 @@ namespace rg3::llvm::visitors cpp::ClassProperty& newProperty = foundProperties.emplace_back(); newProperty.sAlias = newProperty.sName = cxxFieldDecl->getNameAsString(); - // Fill type info (and decl info) + // Collect common info fillTypeStatementFromLLVMEntry(newProperty.sTypeInfo, cxxFieldDecl); // Save other info diff --git a/LLVM/source/Visitors/CxxRouterVisitor.cpp b/LLVM/source/Visitors/CxxRouterVisitor.cpp index 8591b3f..a2f10d8 100644 --- a/LLVM/source/Visitors/CxxRouterVisitor.cpp +++ b/LLVM/source/Visitors/CxxRouterVisitor.cpp @@ -376,6 +376,8 @@ namespace rg3::llvm::visitors // Type is handled here const size_t iKnownTypes = m_vFoundTypes.size(); + + // In my case pAsTypedefDecl contains correct pointer in glm case, but... it's weird. Anyway I'm unable to deconstruct this inside that func bHandled = handleAnnotationBasedType(pInnerType, annotation, ctx, false); // Ok, last inserted type is our new type (?) @@ -485,6 +487,7 @@ namespace rg3::llvm::visitors ExtraFunctionsFilter functionsFilter { annotation.knownFunctions }; CxxTemplateSpecializationVisitor visitor { newConfig, pTemplateSpecDecl, !annotation.knownProperties.empty(), !annotation.knownFunctions.empty(), propertiesFilter, functionsFilter }; + // Here we need to find a correct specialization, but for glm there are no specialization at all... if (auto* pSpecializedTemplate = pTemplateSpecDecl->getSpecializedTemplate()) { clang::Decl* pTargetDecl = nullptr; @@ -498,6 +501,7 @@ namespace rg3::llvm::visitors pTargetDecl = classTemplateDecl; break; } + // wrong! } } diff --git a/PyBind/source/PyTypeBase.cpp b/PyBind/source/PyTypeBase.cpp index 822f3e8..6869cc3 100644 --- a/PyBind/source/PyTypeBase.cpp +++ b/PyBind/source/PyTypeBase.cpp @@ -1,4 +1,5 @@ #include +#include namespace rg3::pybind @@ -17,16 +18,16 @@ namespace rg3::pybind switch (m_base->getKind()) { case cpp::TypeKind::TK_NONE: - str = "none"; + str = "[INVALID TYPE]"; break; case cpp::TypeKind::TK_TRIVIAL: - str = m_base->getPrettyName(); + str = fmt::format("{} [TRIVIAL]", m_base->getPrettyName()); break; case cpp::TypeKind::TK_ENUM: - str = "enum " + m_base->getPrettyName(); + str = fmt::format("{} [ENUM]", m_base->getPrettyName()); break; case cpp::TypeKind::TK_STRUCT_OR_CLASS: - str = "class " + m_base->getPrettyName(); + str = fmt::format("{} [CLASS/STRUCT]", m_base->getPrettyName()); break; } diff --git a/Tests/PyIntegration/tests.py b/Tests/PyIntegration/tests.py index eacea7d..93004db 100644 --- a/Tests/PyIntegration/tests.py +++ b/Tests/PyIntegration/tests.py @@ -523,3 +523,172 @@ def test_check_type_annotations(): assert analyzer.types[1].properties[1].name == "rLong" assert analyzer.types[1].properties[1].alias == "Long" assert len(analyzer.types[1].functions) == 0 + + +def test_check_member_property_type_ref(): + analyzer: rg3py.CodeAnalyzer = rg3py.CodeAnalyzer.make() + + analyzer.set_code(""" +namespace cool::name::space::at::all { + /// @runtime + struct Vector3 + { // doesn't matter + }; + + template + struct TVector2 + { + T x; + T y; + }; + + namespace rg3 { + template struct RegisterType {}; + template <> struct + __attribute__((annotate("RG3_RegisterRuntime"))) + __attribute__((annotate("RG3_RegisterField[x]"))) + __attribute__((annotate("RG3_RegisterField[y]"))) + RegisterType> { + using Type = cool::name::space::at::all::TVector2; + }; + } +} + +using namespace cool::name::space::at::all; // to avoid of full form later + +namespace engine::render +{ + using Vec2I = TVector2; +} + +using namespace engine::render; + +namespace engine { + /// @runtime + struct TransformComponent + { + /// @property(vPos) + const Vector3 position{}; + + /// @property(vDir) + Vector3 direction{}; + + /// @property(vUP) + TVector2 up {}; + + /// @property(vTex0UV) + Vec2I texUV {}; + }; +} + """) + + analyzer.set_cpp_standard(rg3py.CppStandard.CXX_17) + analyzer.analyze() + + assert len(analyzer.issues) == 0 + assert len(analyzer.types) == 3 + + # Base check (all check in C++ tests) + assert analyzer.types[2].pretty_name == "engine::TransformComponent" + + as_class: rg3py.CppClass = analyzer.types[2] + assert len(as_class.properties) == 4 + + assert as_class.properties[0].name == "position" + assert as_class.properties[0].alias == "vPos" + assert as_class.properties[0].type_info.get_name() == "cool::name::space::at::all::Vector3" + + assert as_class.properties[1].name == "direction" + assert as_class.properties[1].alias == "vDir" + assert as_class.properties[1].type_info.get_name() == "cool::name::space::at::all::Vector3" + + assert as_class.properties[2].name == "up" + assert as_class.properties[2].alias == "vUP" + assert as_class.properties[2].type_info.get_name() == "cool::name::space::at::all::TVector2" + + assert as_class.properties[3].name == "texUV" + assert as_class.properties[3].alias == "vTex0UV" + assert as_class.properties[3].type_info.get_name() == "engine::render::Vec2I" + + +def test_check_function_arg_property_type_ref(): + analyzer: rg3py.CodeAnalyzer = rg3py.CodeAnalyzer.make() + + analyzer.set_code(""" + namespace cool::name::space::at::all { + /// @runtime + struct Vector3 + { // doesn't matter + }; + + template + struct TVector2 + { + T x; + T y; + }; + + namespace rg3 { + template struct RegisterType {}; + template <> struct + __attribute__((annotate("RG3_RegisterRuntime"))) + __attribute__((annotate("RG3_RegisterField[x]"))) + __attribute__((annotate("RG3_RegisterField[y]"))) + RegisterType> { + using Type = cool::name::space::at::all::TVector2; + }; + } + } + + using namespace cool::name::space::at::all; // to avoid of full form later + + namespace engine::render + { + using Vec2I = TVector2; + } + + using namespace engine::render; + + namespace engine { + /// @runtime + struct TransformComponent + { + void CalculateTangent(const Vector3& vIn, const TVector2& vCorrectionScreenSpace, const Vec2I& inv, Vector3& vOut) const; + static TVector2 MakeV2F_FromI(const Vec2I& v2I); + }; + } + """) + + analyzer.set_cpp_standard(rg3py.CppStandard.CXX_17) + analyzer.analyze() + + assert len(analyzer.issues) == 0 + assert len(analyzer.types) == 3 + + assert analyzer.types[2].pretty_name == "engine::TransformComponent" + as_class: rg3py.CppClass = analyzer.types[2] + + assert len(as_class.functions) == 2 + assert len(as_class.properties) == 0 + + assert as_class.functions[0].name == "CalculateTangent" + assert as_class.functions[0].return_type.is_void + assert as_class.functions[0].is_const + assert as_class.functions[0].return_type.get_name() == "void" + assert len(as_class.functions[0].arguments) == 4 + assert as_class.functions[0].arguments[0].name == "vIn" + assert as_class.functions[0].arguments[0].type_info.get_name() == "cool::name::space::at::all::Vector3" + assert as_class.functions[0].arguments[1].name == "vCorrectionScreenSpace" + assert as_class.functions[0].arguments[1].type_info.get_name() == "cool::name::space::at::all::TVector2" + assert as_class.functions[0].arguments[2].name == "inv" + assert as_class.functions[0].arguments[2].type_info.get_name() == "engine::render::Vec2I" + assert as_class.functions[0].arguments[3].name == "vOut" + assert as_class.functions[0].arguments[3].type_info.get_name() == "cool::name::space::at::all::Vector3" + + assert as_class.functions[1].name == "MakeV2F_FromI" + assert as_class.functions[1].is_static + assert as_class.functions[1].return_type.get_name() == "cool::name::space::at::all::TVector2" + assert as_class.functions[1].return_type.is_void == False + assert len(as_class.functions[1].arguments) == 1 + assert as_class.functions[1].arguments[0].name == "v2I" + assert as_class.functions[1].arguments[0].type_info.get_name() == "engine::render::Vec2I" diff --git a/Tests/Unit/source/Tests_MemberFunctions.cpp b/Tests/Unit/source/Tests_MemberFunctions.cpp index 933e8f2..72e6347 100644 --- a/Tests/Unit/source/Tests_MemberFunctions.cpp +++ b/Tests/Unit/source/Tests_MemberFunctions.cpp @@ -275,4 +275,94 @@ RegisterType> { ASSERT_EQ(asClass1->getFunctions()[0].vArguments[0].sType.sTypeRef.getRefName(), "int"); ASSERT_EQ(asClass1->getFunctions()[0].vArguments[0].sType.bIsPtrConst, true); ASSERT_EQ(asClass1->getFunctions()[0].vArguments[0].sType.bIsReference, true); +} + +TEST_F(Tests_MemberFunctions, CheckMemberPropertyTypeReferenceForm) +{ + g_Analyzer->setSourceCode(R"( +namespace cool::name::space::at::all { + /// @runtime + struct Vector3 + { // doesn't matter + }; + + template + struct TVector2 + { + T x; + T y; + }; + + namespace rg3 { + template struct RegisterType {}; + template <> struct + __attribute__((annotate("RG3_RegisterRuntime"))) + __attribute__((annotate("RG3_RegisterField[x]"))) + __attribute__((annotate("RG3_RegisterField[y]"))) + RegisterType> { + using Type = cool::name::space::at::all::TVector2; + }; + } +} + +using namespace cool::name::space::at::all; // to avoid of full form later + +namespace engine::render +{ + using Vec2I = TVector2; +} + +using namespace engine::render; + +namespace engine { + /// @runtime + struct TransformComponent + { + /// @property(vPos) + const Vector3 position{}; + + /// @property(vDir) + Vector3 direction{}; + + /// @property(vUP) + TVector2 up {}; + + /// @property(vTex0UV) + Vec2I texUV {}; + }; +} +)"); + + g_Analyzer->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_20; + + const auto analyzeResult = g_Analyzer->analyze(); + + ASSERT_TRUE(analyzeResult.vIssues.empty()) << "No issues should be here"; + ASSERT_EQ(analyzeResult.vFoundTypes.size(), 3) << "Only 3 type should be here"; + + ASSERT_EQ(analyzeResult.vFoundTypes[0]->getPrettyName(), "cool::name::space::at::all::Vector3"); + ASSERT_EQ(analyzeResult.vFoundTypes[0]->getKind(), rg3::cpp::TypeKind::TK_STRUCT_OR_CLASS); + ASSERT_EQ(analyzeResult.vFoundTypes[1]->getPrettyName(), "cool::name::space::at::all::TVector2"); + ASSERT_EQ(analyzeResult.vFoundTypes[1]->getKind(), rg3::cpp::TypeKind::TK_STRUCT_OR_CLASS); + ASSERT_EQ(analyzeResult.vFoundTypes[2]->getPrettyName(), "engine::TransformComponent"); + ASSERT_EQ(analyzeResult.vFoundTypes[2]->getKind(), rg3::cpp::TypeKind::TK_STRUCT_OR_CLASS); + + auto* asClass = reinterpret_cast(analyzeResult.vFoundTypes[2].get()); + ASSERT_EQ(asClass->getProperties().size(), 4); + + ASSERT_EQ(asClass->getProperties()[0].sName, "position"); + ASSERT_EQ(asClass->getProperties()[0].sAlias, "vPos"); + ASSERT_EQ(asClass->getProperties()[0].sTypeInfo.sTypeRef.getRefName(), "cool::name::space::at::all::Vector3"); + + ASSERT_EQ(asClass->getProperties()[1].sName, "direction"); + ASSERT_EQ(asClass->getProperties()[1].sAlias, "vDir"); + ASSERT_EQ(asClass->getProperties()[1].sTypeInfo.sTypeRef.getRefName(), "cool::name::space::at::all::Vector3"); + + ASSERT_EQ(asClass->getProperties()[2].sName, "up"); + ASSERT_EQ(asClass->getProperties()[2].sAlias, "vUP"); + ASSERT_EQ(asClass->getProperties()[2].sTypeInfo.sTypeRef.getRefName(), "cool::name::space::at::all::TVector2"); + + ASSERT_EQ(asClass->getProperties()[3].sName, "texUV"); + ASSERT_EQ(asClass->getProperties()[3].sAlias, "vTex0UV"); + ASSERT_EQ(asClass->getProperties()[3].sTypeInfo.sTypeRef.getRefName(), "engine::render::Vec2I"); } \ No newline at end of file diff --git a/Tests/Unit/source/Tests_ThirdPartyAnalysis.cpp b/Tests/Unit/source/Tests_ThirdPartyAnalysis.cpp new file mode 100644 index 0000000..83b02c2 --- /dev/null +++ b/Tests/Unit/source/Tests_ThirdPartyAnalysis.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include +#include +#include + + +class Tests_ThirdPartyAnalysis : public ::testing::Test +{ + protected: + void SetUp() override + { + g_Analyzer = std::make_unique(); + g_Analyzer->getCompilerConfig().vIncludes.emplace_back("Unit/test_headers/ThirdParty/glm"); // Add glm to lookup + } + + void TearDown() override + { + g_Analyzer = nullptr; + } + + protected: + std::unique_ptr g_Analyzer { nullptr }; +}; + +#if 0 +TEST_F(Tests_ThirdPartyAnalysis, CheckGlmVec2AnonymousReg) +{ + g_Analyzer->setSourceCode(R"( +#ifdef __RG3__ +#include + +// Types +template struct RegisterType {}; + +template <> struct + __attribute__((annotate("RG3_RegisterRuntime"))) +RegisterType { + using Type = glm::vec2; +}; + +#endif +)"); + + g_Analyzer->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_17; + + const auto analyzeResult = g_Analyzer->analyze(); + + ASSERT_TRUE(analyzeResult.vIssues.empty()) << "No issues should be here"; + ASSERT_EQ(analyzeResult.vFoundTypes.size(), 1) << "Only 1 type should be here"; + // **** BUG HERE: partial specializations are not supported! This test will work! *** +} +#endif \ No newline at end of file diff --git a/Tests/Unit/test_headers/ThirdParty/README.md b/Tests/Unit/test_headers/ThirdParty/README.md new file mode 100644 index 0000000..851de25 --- /dev/null +++ b/Tests/Unit/test_headers/ThirdParty/README.md @@ -0,0 +1,4 @@ +RG3/Tests/Unit/test_headers/ThirdParty +--------------------------------------- + +This folder contains test libraries which used **only for code analysis testing purposes**. \ No newline at end of file diff --git a/Tests/Unit/test_headers/ThirdParty/glm b/Tests/Unit/test_headers/ThirdParty/glm new file mode 160000 index 0000000..4137519 --- /dev/null +++ b/Tests/Unit/test_headers/ThirdParty/glm @@ -0,0 +1 @@ +Subproject commit 4137519418a933e5863eea7c3ac53890ae7faf9d