Skip to content

[cxx-interop] Fix unqualified name lookup failure #82840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/swift/AST/ClangModuleLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ class ClangModuleLoader : public ModuleLoader {
DeclContext *newContext,
ClangInheritanceInfo inheritance) = 0;

/// Checks if \param decl is the original method or a clone from a base class
virtual bool isClonedMemberDecl(ValueDecl *decl) = 0;
/// Returnes the original method if \param decl is a clone from a base class
virtual ValueDecl *getOriginalForClonedMember(const ValueDecl *decl) = 0;

/// Emits diagnostics for any declarations named name
/// whose direct declaration context is a TU.
Expand Down
2 changes: 1 addition & 1 deletion include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ class ClangImporter final : public ClangModuleLoader {
ValueDecl *importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext,
ClangInheritanceInfo inheritance) override;

bool isClonedMemberDecl(ValueDecl *decl) override;
ValueDecl *getOriginalForClonedMember(const ValueDecl *decl) override;

/// Emits diagnostics for any declarations named name
/// whose direct declaration context is a TU.
Expand Down
16 changes: 15 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -910,15 +910,29 @@ static ModuleDecl *getModuleContextForNameLookupForCxxDecl(const Decl *decl) {
if (!ctx.LangOpts.EnableCXXInterop)
return nullptr;

// When we clone members for base classes the cloned members have no
// corresponding Clang nodes. Look up the original imported declaration to
// figure out what Clang module does the cloned member originate from.
bool isClonedMember = false;
if (auto VD = dyn_cast<ValueDecl>(decl))
if (auto loader = ctx.getClangModuleLoader())
if (auto original = loader->getOriginalForClonedMember(VD)) {
isClonedMember = true;
decl = original;
}

if (!decl->hasClangNode())
return nullptr;

auto parentModule = decl->getModuleContext();

// We only need to look for the real parent module when the existing parent
// is the imported header module.
if (!parentModule->isClangHeaderImportModule())
if (!parentModule->isClangHeaderImportModule()) {
if (isClonedMember)
return parentModule;
return nullptr;
}

auto clangModule = decl->getClangDecl()->getOwningModule();
if (!clangModule)
Expand Down
25 changes: 14 additions & 11 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5524,10 +5524,11 @@ const clang::CXXMethodDecl *getCalledBaseCxxMethod(FuncDecl *baseMember) {

// Construct a Swift method that represents the synthesized C++ method
// that invokes the base C++ method.
FuncDecl *synthesizeBaseFunctionDeclCall(ClangImporter &impl, ASTContext &ctx,
NominalTypeDecl *derivedStruct,
NominalTypeDecl *baseStruct,
FuncDecl *baseMember) {
static FuncDecl *synthesizeBaseFunctionDeclCall(ClangImporter &impl,
ASTContext &ctx,
NominalTypeDecl *derivedStruct,
NominalTypeDecl *baseStruct,
FuncDecl *baseMember) {
auto *cxxMethod = getCalledBaseCxxMethod(baseMember);
if (!cxxMethod)
return nullptr;
Expand Down Expand Up @@ -6317,7 +6318,7 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
auto namedMember = dyn_cast<ValueDecl>(member);
if (!namedMember || !namedMember->hasName() ||
namedMember->getName().getBaseName() != name ||
clangModuleLoader->isClonedMemberDecl(namedMember))
clangModuleLoader->getOriginalForClonedMember(namedMember))
continue;

auto *imported = clangModuleLoader->importBaseMemberDecl(
Expand Down Expand Up @@ -7670,22 +7671,24 @@ ValueDecl *ClangImporter::Implementation::importBaseMemberDecl(
if (known == clonedBaseMembers.end()) {
ValueDecl *cloned = cloneBaseMemberDecl(decl, newContext, inheritance);
known = clonedBaseMembers.insert({key, cloned}).first;
clonedMembers.insert(cloned);
clonedMembers.insert(std::make_pair(cloned, decl));
}

return known->second;
}

bool ClangImporter::Implementation::isClonedMemberDecl(ValueDecl *decl) {
ValueDecl *ClangImporter::Implementation::getOriginalForClonedMember(
const ValueDecl *decl) {
// If this is a cloned decl, we don't want to reclone it
// Otherwise, we may end up with multiple copies of the same method
if (!decl->hasClangNode()) {
// Skip decls with a clang node as those will never be a clone
auto result = clonedMembers.find(decl);
return result != clonedMembers.end();
if (result != clonedMembers.end())
return result->getSecond();
}

return false;
return nullptr;
}

size_t ClangImporter::Implementation::getImportedBaseMemberDeclArity(
Expand All @@ -7704,8 +7707,8 @@ ClangImporter::importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext,
return Impl.importBaseMemberDecl(decl, newContext, inheritance);
}

bool ClangImporter::isClonedMemberDecl(ValueDecl *decl) {
return Impl.isClonedMemberDecl(decl);
ValueDecl *ClangImporter::getOriginalForClonedMember(const ValueDecl *decl) {
return Impl.getOriginalForClonedMember(decl);
}

void ClangImporter::diagnoseTopLevelValue(const DeclName &name) {
Expand Down
6 changes: 3 additions & 3 deletions lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
llvm::DenseMap<std::pair<ValueDecl *, DeclContext *>, ValueDecl *>
clonedBaseMembers;

// Store all methods that result from cloning a base member
llvm::DenseSet<ValueDecl *> clonedMembers;
// Map all cloned methods back to the original member
llvm::DenseMap<ValueDecl *, ValueDecl *> clonedMembers;

public:
llvm::DenseMap<const clang::ParmVarDecl*, FuncDecl*> defaultArgGenerators;
Expand All @@ -702,7 +702,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
ValueDecl *importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext,
ClangInheritanceInfo inheritance);

bool isClonedMemberDecl(ValueDecl *decl);
ValueDecl *getOriginalForClonedMember(const ValueDecl *decl);

static size_t getImportedBaseMemberDeclArity(const ValueDecl *valueDecl);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ struct IIOne : IOne {
struct IIIOne : IIOne {
int methodIII(void) const { return -111; }
};

class Base {
public:
bool baseMethod() const { return true; }
};

namespace Bar {
class Derived : public Base {};
} // namespace Bar
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -cxx-interoperability-mode=default)
//
// REQUIRES: executable_test

import InheritedLookup
import StdlibUnittest

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -cxx-interoperability-mode=default -enable-upcoming-feature MemberImportVisibility)
//
// REQUIRES: executable_test
// REQUIRES: swift_feature_MemberImportVisibility

import InheritedLookup
import StdlibUnittest

var InheritedMemberTestSuite = TestSuite("Test if inherited lookup works")

extension Bar.Derived {
public func callBase() {
let _ = baseMethod()
}
}

InheritedMemberTestSuite.test("Look up base methods from extensions") {
let a = Bar.Derived()
a.callBase()
}

runAllTests()