From 8a0a6b6cd81737ea993db022936fc5ef90c18a85 Mon Sep 17 00:00:00 2001 From: Krishna-13-cyber Date: Thu, 18 Jun 2026 13:16:17 +0530 Subject: [PATCH 1/5] Add support for inherited members via ShadowDecl --- lib/CppInterOp/CppInterOp.cpp | 67 ++++++++++++++++++++ unittests/CppInterOp/ScopeReflectionTest.cpp | 20 ++++++ 2 files changed, 87 insertions(+) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 337f1dff7..9cb629347 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1193,6 +1193,39 @@ clang::NamedDecl* LookupUnqualified(clang::Sema& S, return (clang::NamedDecl*)-1; } + +static clang::UsingShadowDecl *CreateInheritedUsingShadow( + clang::CXXRecordDecl *Record, clang::NamedDecl *Target) { + if (!Record || !Target) + return nullptr; + + if (auto *Shadow = llvm::dyn_cast(Target)) + Target = Shadow->getTargetDecl(); + + if (!Target) + return nullptr; + + if (Target->getDeclContext() == Record) + return llvm::dyn_cast(Target); + + clang::ASTContext &C = Record->getASTContext(); + clang::DeclarationNameInfo NameInfo(Target->getDeclName(), + clang::SourceLocation()); + auto *Using = clang::UsingDecl::Create(C, Record, clang::SourceLocation(), + clang::NestedNameSpecifierLoc(), + NameInfo, false); + Using->setImplicit(true); + + auto *Shadow = clang::UsingShadowDecl::Create( + C, Record, clang::SourceLocation(), Target->getDeclName(), Using, + Target); + Using->addShadowDecl(Shadow); + Shadow->setAccess(Target->getAccess()); + Record->addDecl(Shadow); + + return Shadow; +} + // Cheap probe: does any namespace from `DC` up to TU carry at least // one using-directive? Gates the synthetic-DRef-chain build below so // the common case (no using-directives anywhere on the path) doesn't @@ -1230,6 +1263,40 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { if (ND && ND != (clang::NamedDecl*)-1) return INTEROP_RETURN(ND->getCanonicalDecl()); + // Qualified lookup can miss inherited members in class scope. Try the + // record's own lookup, which includes direct members, and then fall back + // to unqualified lookup inside the record to honor base-class member + // visibility semantics. + if (Within) { + if (auto* RD = llvm::dyn_cast(Within)) { + clang::DeclarationName DName = &getSema().Context.Idents.get(name); + auto Decls = RD->lookup(DName); + clang::NamedDecl* FoundND = nullptr; + for (auto* D : Decls) { + if (auto* Named = llvm::dyn_cast(D)) { + if (!FoundND) + FoundND = Named; + else + return INTEROP_RETURN(nullptr); + } + } + if (FoundND) + return INTEROP_RETURN((TCppScope_t)(FoundND->getCanonicalDecl())); + + // Qualified lookup may still miss inherited class members in some + // record contexts. Use unqualified lookup from a synthesized point + // inside the class to traverse base classes and find the member. + auto* ND2 = LookupUnqualified(getSema(), DName, Within); + if (ND2 && ND2 != (clang::NamedDecl*)-1) { + if (auto* Shadow = CreateInheritedUsingShadow(RD, ND2)) + return INTEROP_RETURN((TCppScope_t)(Shadow->getCanonicalDecl())); + return INTEROP_RETURN((TCppScope_t)(ND2->getCanonicalDecl())); + } + if (ND2 == (clang::NamedDecl*)-1) + return INTEROP_RETURN(nullptr); + } + } + // Slow path: only when qualified lookup missed AND `Within` is a // namespace whose enclosing chain carries at least one using-directive // (the only reason qualified-vs-unqualified disagree at namespace diff --git a/unittests/CppInterOp/ScopeReflectionTest.cpp b/unittests/CppInterOp/ScopeReflectionTest.cpp index 9ad8c1d20..da0e38d8b 100644 --- a/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -702,6 +702,26 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamed) { EXPECT_EQ(Cpp::GetQualifiedName(std_ns), "std"); EXPECT_EQ(Cpp::GetQualifiedName(std_string_class), "std::string"); EXPECT_EQ(Cpp::GetQualifiedName(std_string_npos_var), "std::basic_string::npos"); + + Interp->declare(R"( + struct S { + typedef int Val; + }; + + struct S1 : public S { + /* empty */ + }; + )"); + +Cpp::TCppScope_t strt_S = Cpp::GetNamed("S", nullptr); +Cpp::TCppScope_t strt_S_Val = Cpp::GetNamed("Val", strt_S); +Cpp::TCppScope_t strt_S1 = Cpp::GetNamed("S1", nullptr); +Cpp::TCppScope_t strt_S1_Val = Cpp::GetNamed("Val", strt_S1); + +EXPECT_EQ(Cpp::GetQualifiedName(strt_S), "S"); +EXPECT_EQ(Cpp::GetQualifiedName(strt_S_Val), "S::Val"); +EXPECT_EQ(Cpp::GetQualifiedName(strt_S1), "S1"); +EXPECT_EQ(Cpp::GetQualifiedName(strt_S1_Val), "S1::Val"); } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamedWithUsing) { From c51f1c9ef6a5a15799f0ec3ef6d609b43fa45995 Mon Sep 17 00:00:00 2001 From: Krishna-13-cyber Date: Sat, 27 Jun 2026 19:23:55 +0530 Subject: [PATCH 2/5] Update with removing casting to fix CI builds --- lib/CppInterOp/CppInterOp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 9cb629347..ee6b82929 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1281,7 +1281,7 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { } } if (FoundND) - return INTEROP_RETURN((TCppScope_t)(FoundND->getCanonicalDecl())); + return INTEROP_RETURN((FoundND->getCanonicalDecl())); // Qualified lookup may still miss inherited class members in some // record contexts. Use unqualified lookup from a synthesized point @@ -1289,8 +1289,8 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { auto* ND2 = LookupUnqualified(getSema(), DName, Within); if (ND2 && ND2 != (clang::NamedDecl*)-1) { if (auto* Shadow = CreateInheritedUsingShadow(RD, ND2)) - return INTEROP_RETURN((TCppScope_t)(Shadow->getCanonicalDecl())); - return INTEROP_RETURN((TCppScope_t)(ND2->getCanonicalDecl())); + return INTEROP_RETURN((Shadow->getCanonicalDecl())); + return INTEROP_RETURN((ND2->getCanonicalDecl())); } if (ND2 == (clang::NamedDecl*)-1) return INTEROP_RETURN(nullptr); From 3f3854eed745b11c0f2b13f26ed7c9561ab601f2 Mon Sep 17 00:00:00 2001 From: Krishna-13-cyber Date: Sat, 27 Jun 2026 23:00:39 +0530 Subject: [PATCH 3/5] Update tests --- unittests/CppInterOp/ScopeReflectionTest.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/unittests/CppInterOp/ScopeReflectionTest.cpp b/unittests/CppInterOp/ScopeReflectionTest.cpp index da0e38d8b..4018f9277 100644 --- a/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -702,7 +702,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamed) { EXPECT_EQ(Cpp::GetQualifiedName(std_ns), "std"); EXPECT_EQ(Cpp::GetQualifiedName(std_string_class), "std::string"); EXPECT_EQ(Cpp::GetQualifiedName(std_string_npos_var), "std::basic_string::npos"); - Interp->declare(R"( struct S { typedef int Val; @@ -713,15 +712,15 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamed) { }; )"); -Cpp::TCppScope_t strt_S = Cpp::GetNamed("S", nullptr); -Cpp::TCppScope_t strt_S_Val = Cpp::GetNamed("Val", strt_S); -Cpp::TCppScope_t strt_S1 = Cpp::GetNamed("S1", nullptr); -Cpp::TCppScope_t strt_S1_Val = Cpp::GetNamed("Val", strt_S1); + Cpp::DeclRef strt_S = Cpp::GetNamed("S", nullptr); + Cpp::DeclRef strt_S_Val = Cpp::GetNamed("Val", strt_S); + Cpp::DeclRef strt_S1 = Cpp::GetNamed("S1", nullptr); + Cpp::DeclRef strt_S1_Val = Cpp::GetNamed("Val", strt_S1); -EXPECT_EQ(Cpp::GetQualifiedName(strt_S), "S"); -EXPECT_EQ(Cpp::GetQualifiedName(strt_S_Val), "S::Val"); -EXPECT_EQ(Cpp::GetQualifiedName(strt_S1), "S1"); -EXPECT_EQ(Cpp::GetQualifiedName(strt_S1_Val), "S1::Val"); + EXPECT_EQ(Cpp::GetQualifiedName(strt_S), "S"); + EXPECT_EQ(Cpp::GetQualifiedName(strt_S_Val), "S::Val"); + EXPECT_EQ(Cpp::GetQualifiedName(strt_S1), "S1"); + EXPECT_EQ(Cpp::GetQualifiedName(strt_S1_Val), "S1::Val"); } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamedWithUsing) { From 69db9990dc8a5a2561205a48f7ba1f35d07c2336 Mon Sep 17 00:00:00 2001 From: Krishna-13-cyber Date: Tue, 30 Jun 2026 22:46:44 +0530 Subject: [PATCH 4/5] Fixture for breaking versions --- lib/CppInterOp/CppInterOp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index ee6b82929..fc6c921c9 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1215,6 +1215,7 @@ static clang::UsingShadowDecl *CreateInheritedUsingShadow( clang::NestedNameSpecifierLoc(), NameInfo, false); Using->setImplicit(true); + Using->setAccess(Target->getAccess()); auto *Shadow = clang::UsingShadowDecl::Create( C, Record, clang::SourceLocation(), Target->getDeclName(), Using, @@ -1222,6 +1223,7 @@ static clang::UsingShadowDecl *CreateInheritedUsingShadow( Using->addShadowDecl(Shadow); Shadow->setAccess(Target->getAccess()); Record->addDecl(Shadow); + Record->addDecl(Using); return Shadow; } From 5589a8b38ccaad7cbf460ef98743ed9a6b32e939 Mon Sep 17 00:00:00 2001 From: Krishna-13-cyber Date: Wed, 1 Jul 2026 23:23:41 +0530 Subject: [PATCH 5/5] Remove the calls for canonical decls --- lib/CppInterOp/CppInterOp.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index fc6c921c9..743d506bf 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1215,15 +1215,13 @@ static clang::UsingShadowDecl *CreateInheritedUsingShadow( clang::NestedNameSpecifierLoc(), NameInfo, false); Using->setImplicit(true); - Using->setAccess(Target->getAccess()); auto *Shadow = clang::UsingShadowDecl::Create( C, Record, clang::SourceLocation(), Target->getDeclName(), Using, Target); + Using->addShadowDecl(Shadow); - Shadow->setAccess(Target->getAccess()); Record->addDecl(Shadow); - Record->addDecl(Using); return Shadow; } @@ -1263,7 +1261,7 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { // null, so TU-level using-directives are already handled there. auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) - return INTEROP_RETURN(ND->getCanonicalDecl()); + return INTEROP_RETURN(ND); // Qualified lookup can miss inherited members in class scope. Try the // record's own lookup, which includes direct members, and then fall back @@ -1283,16 +1281,18 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { } } if (FoundND) - return INTEROP_RETURN((FoundND->getCanonicalDecl())); + return INTEROP_RETURN((FoundND)); // Qualified lookup may still miss inherited class members in some // record contexts. Use unqualified lookup from a synthesized point // inside the class to traverse base classes and find the member. auto* ND2 = LookupUnqualified(getSema(), DName, Within); - if (ND2 && ND2 != (clang::NamedDecl*)-1) { + if (ND2 == reinterpret_cast(-1)) + return INTEROP_RETURN(nullptr); + if (ND2) { if (auto* Shadow = CreateInheritedUsingShadow(RD, ND2)) - return INTEROP_RETURN((Shadow->getCanonicalDecl())); - return INTEROP_RETURN((ND2->getCanonicalDecl())); + return INTEROP_RETURN(Shadow); + return INTEROP_RETURN(ND2); } if (ND2 == (clang::NamedDecl*)-1) return INTEROP_RETURN(nullptr); @@ -1309,7 +1309,7 @@ DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { clang::DeclarationName DName = &getSema().Context.Idents.get(name); ND = LookupUnqualified(getSema(), DName, Within); if (ND && ND != (clang::NamedDecl*)-1) - return INTEROP_RETURN(ND->getCanonicalDecl()); + return INTEROP_RETURN(ND); return INTEROP_RETURN(nullptr); }