From 0f8b97aae4d01081a5780d735a95016e75ae7d6e Mon Sep 17 00:00:00 2001 From: John Hui Date: Fri, 31 Jan 2025 20:20:23 -0800 Subject: [PATCH 1/3] [cxx-interop] Import non-public inherited members This patch is follow-up work from #78942 and concurrent with #79093. It imports non-public members, which were previously not being imported. Once #79093 (SWIFT_PRIVATE_FILEID) is merged in, these inherited members will be available within certain Swift extensions. --- include/swift/AST/ClangModuleLoader.h | 16 +- include/swift/ClangImporter/ClangImporter.h | 3 +- .../ClangImporter/ClangImporterRequests.h | 31 ++- lib/ClangImporter/ClangImporter.cpp | 91 ++++--- lib/ClangImporter/ImportDecl.cpp | 86 +++++-- lib/ClangImporter/ImporterImpl.h | 8 +- .../functions-module-interface.swift | 228 +++++++++++++----- .../using-base-members-typechecker.swift | 4 +- 8 files changed, 340 insertions(+), 127 deletions(-) diff --git a/include/swift/AST/ClangModuleLoader.h b/include/swift/AST/ClangModuleLoader.h index 31923049d0083..af9c5feff08c4 100644 --- a/include/swift/AST/ClangModuleLoader.h +++ b/include/swift/AST/ClangModuleLoader.h @@ -202,10 +202,18 @@ class ClangModuleLoader : public ModuleLoader { /// Imports a clang decl directly, rather than looking up its name. virtual Decl *importDeclDirectly(const clang::NamedDecl *decl) = 0; - /// Imports a clang decl from a base class, cloning it for \param newContext - /// if it wasn't cloned for this specific context before. - virtual ValueDecl *importBaseMemberDecl(ValueDecl *decl, - DeclContext *newContext) = 0; + /// Clones an imported \param decl from its base class to its derived class + /// \param newContext where it is inherited. Its access level is determined + /// with respect to \param inheritance, which signifies whether \param decl + /// was inherited via C++ public/protected/private inheritance. + /// + /// This function uses a cache so that it is idempotent; successive + /// invocations will only generate one cloned ValueDecl (and all return + /// a pointer to it). Returns a NULL pointer upon failure. + virtual ValueDecl * + importBaseMemberDecl(ValueDecl *decl, + DeclContext *newContext, + clang::AccessSpecifier inheritance) = 0; /// Emits diagnostics for any declarations named name /// whose direct declaration context is a TU. diff --git a/include/swift/ClangImporter/ClangImporter.h b/include/swift/ClangImporter/ClangImporter.h index 07a264819e9bf..9560facd5a5a8 100644 --- a/include/swift/ClangImporter/ClangImporter.h +++ b/include/swift/ClangImporter/ClangImporter.h @@ -658,7 +658,8 @@ class ClangImporter final : public ClangModuleLoader { Decl *importDeclDirectly(const clang::NamedDecl *decl) override; ValueDecl *importBaseMemberDecl(ValueDecl *decl, - DeclContext *newContext) override; + DeclContext *newContext, + clang::AccessSpecifier inheritance) override; /// Emits diagnostics for any declarations named name /// whose direct declaration context is a TU. diff --git a/include/swift/ClangImporter/ClangImporterRequests.h b/include/swift/ClangImporter/ClangImporterRequests.h index f91f89b755db9..cadd0b8de5679 100644 --- a/include/swift/ClangImporter/ClangImporterRequests.h +++ b/include/swift/ClangImporter/ClangImporterRequests.h @@ -24,6 +24,7 @@ #include "swift/Basic/Statistic.h" #include "swift/ClangImporter/ClangImporter.h" #include "clang/AST/Type.h" +#include "clang/Basic/Specifiers.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/TinyPtrVector.h" @@ -132,25 +133,33 @@ class CXXNamespaceMemberLookup }; /// The input type for a record member lookup request. +/// +/// These lookups may be requested recursively in the case of inheritance, +/// for which we separately keep track of the derived class where we started +/// looking (startDecl) and the access level for the current inheritance. struct ClangRecordMemberLookupDescriptor final { - NominalTypeDecl *recordDecl; - NominalTypeDecl *inheritingDecl; - DeclName name; + NominalTypeDecl *recordDecl; // Where we are currently looking + NominalTypeDecl *inheritingDecl; // Where we started looking from + DeclName name; // What we are looking for + clang::AccessSpecifier inheritance; // public/protected/private inheritance + // (clang::AS_none means no inheritance) ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name) - : recordDecl(recordDecl), inheritingDecl(recordDecl), name(name) { + : recordDecl(recordDecl), inheritingDecl(recordDecl), name(name), + inheritance(clang::AS_none) { assert(isa(recordDecl->getClangDecl())); } friend llvm::hash_code hash_value(const ClangRecordMemberLookupDescriptor &desc) { - return llvm::hash_combine(desc.name, desc.recordDecl, desc.inheritingDecl); + return llvm::hash_combine(desc.name, desc.recordDecl, desc.inheritingDecl, desc.inheritance); } friend bool operator==(const ClangRecordMemberLookupDescriptor &lhs, const ClangRecordMemberLookupDescriptor &rhs) { return lhs.name == rhs.name && lhs.recordDecl == rhs.recordDecl && - lhs.inheritingDecl == rhs.inheritingDecl; + lhs.inheritingDecl == rhs.inheritingDecl && + lhs.inheritance == rhs.inheritance; } friend bool operator!=(const ClangRecordMemberLookupDescriptor &lhs, @@ -163,11 +172,15 @@ struct ClangRecordMemberLookupDescriptor final { // This private constructor should only be used in ClangRecordMemberLookup, // for recursively traversing base classes that inheritingDecl inherites from. - ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name, - NominalTypeDecl *inheritingDecl) - : recordDecl(recordDecl), inheritingDecl(inheritingDecl), name(name) { + ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, + DeclName name, + NominalTypeDecl *inheritingDecl, + clang::AccessSpecifier inheritance) + : recordDecl(recordDecl), inheritingDecl(inheritingDecl), name(name), + inheritance(inheritance) { assert(isa(recordDecl->getClangDecl())); assert(isa(inheritingDecl->getClangDecl())); + assert(inheritance != clang::AS_none && ""); assert(recordDecl != inheritingDecl && "recursive calls should lookup elsewhere"); } diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index ba3eb9b6433b3..19708323a8b7a 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -5938,9 +5938,6 @@ makeBaseClassMemberAccessors(DeclContext *declContext, bodyParams = ParameterList::createEmpty(ctx); } - assert(baseClassVar->getFormalAccess() == AccessLevel::Public && - "base class member must be public"); - auto getterDecl = AccessorDecl::create( ctx, /*FuncLoc=*/SourceLoc(), @@ -5953,7 +5950,7 @@ makeBaseClassMemberAccessors(DeclContext *declContext, : computedType, declContext); getterDecl->setIsTransparent(true); - getterDecl->setAccess(AccessLevel::Public); + getterDecl->copyFormalAccessFrom(computedVar); getterDecl->setBodySynthesizer(useAddress ? synthesizeBaseClassFieldAddressGetterBody : synthesizeBaseClassFieldGetterBody, @@ -5988,7 +5985,7 @@ makeBaseClassMemberAccessors(DeclContext *declContext, : TupleType::getEmpty(ctx), declContext); setterDecl->setIsTransparent(true); - setterDecl->setAccess(AccessLevel::Public); + setterDecl->copyFormalAccessFrom(computedVar); setterDecl->setBodySynthesizer(useAddress ? synthesizeBaseClassFieldAddressSetterBody : synthesizeBaseClassFieldSetterBody, @@ -6041,8 +6038,17 @@ void cloneImportedAttributes(ValueDecl *fromDecl, ValueDecl* toDecl) { } } -static ValueDecl * -cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { +static ValueDecl *cloneBaseMemberDecl(ValueDecl *decl, + DeclContext *newContext, + clang::AccessSpecifier inheritance) { + assert(inheritance != clang::AS_none && + "public/protected/private inheritance was not specified"); + static_assert(AccessLevel::Private < AccessLevel::Public && + "std::min() relies on this ordering"); + // Adjust access according to whichever is more restrictive, between what decl + // was declared with (in its base class) or what it is being inherited with. + AccessLevel access = std::min(decl->getFormalAccess(), + importer::convertClangAccess(inheritance)); ASTContext &context = decl->getASTContext(); if (auto fn = dyn_cast(decl)) { @@ -6068,7 +6074,7 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { fn->getGenericParams(), fn->getParameters(), fn->getResultInterfaceType(), newContext); cloneImportedAttributes(decl, out); - out->copyFormalAccessFrom(fn); + out->setAccess(access); out->setBodySynthesizer(synthesizeBaseClassMethodBody, fn); out->setSelfAccessKind(fn->getSelfAccessKind()); return out; @@ -6086,7 +6092,7 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { subscript->getStaticSpelling(), subscript->getSubscriptLoc(), subscript->getIndices(), subscript->getNameLoc(), subscript->getElementInterfaceType(), newContext, subscript->getGenericParams()); - out->copyFormalAccessFrom(subscript); + out->setAccess(access); out->setAccessors(SourceLoc(), makeBaseClassMemberAccessors(newContext, out, subscript), SourceLoc()); @@ -6110,7 +6116,7 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { out->setInterfaceType(var->getInterfaceType()); out->setIsObjC(var->isObjC()); out->setIsDynamic(var->isDynamic()); - out->copyFormalAccessFrom(var); + out->setAccess(access); out->getASTContext().evaluator.cacheOutput(HasStorageRequest{out}, false); auto accessors = makeBaseClassMemberAccessors(newContext, out, var); out->setAccessors(SourceLoc(), accessors, SourceLoc()); @@ -6136,7 +6142,7 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { typeAlias->getName(), typeAlias->getNameLoc(), typeAlias->getGenericParams(), newContext); out->setUnderlyingType(typeAlias->getUnderlyingType()); - out->copyFormalAccessFrom(typeAlias); + out->setAccess(access); return out; } @@ -6147,7 +6153,7 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) { typeDecl->getLoc(), typeDecl->getLoc(), typeDecl->getName(), typeDecl->getLoc(), nullptr, newContext); out->setUnderlyingType(typeDecl->getInterfaceType()); - out->copyFormalAccessFrom(typeDecl); + out->setAccess(access); return out; } @@ -6159,7 +6165,7 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( NominalTypeDecl *recordDecl = desc.recordDecl; NominalTypeDecl *inheritingDecl = desc.inheritingDecl; DeclName name = desc.name; - + clang::AccessSpecifier inheritance = desc.inheritance; bool inheritedLookup = recordDecl != inheritingDecl; auto &ctx = recordDecl->getASTContext(); @@ -6181,6 +6187,14 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( if (isa(found) && isa(recordDecl)) continue; + // Skip this base class member if it is private. + // + // BUG: private base class members should be inherited but inaccessible. + // Skipping them here may affect accurate overload resolution in cases of + // multiple inheritance (which is currently buggy anyway). + if (inheritedLookup && found->getAccess() == clang::AS_private) + continue; + auto imported = clangModuleLoader->importDeclDirectly(found); if (!imported) continue; @@ -6189,7 +6203,7 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( // by synthesizing getters and setters. if (inheritedLookup) { imported = clangModuleLoader->importBaseMemberDecl( - cast(imported), inheritingDecl); + cast(imported), inheritingDecl, inheritance); if (!imported) continue; } @@ -6207,8 +6221,18 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( namedMember->getName().getBaseName() != name) continue; + auto memberClangDecl = namedMember->getClangDecl(); + + // Skip this base class if this is a case of nested private inheritance. + // + // BUG: private base class members should be inherited but inaccessible. + // Skipping them here may affect accurate overload resolution in cases of + // multiple inheritance (which is currently buggy anyway). + if (memberClangDecl->getAccess() == clang::AS_private) + continue; + if (auto imported = clangModuleLoader->importBaseMemberDecl( - namedMember, inheritingDecl)) + namedMember, inheritingDecl, inheritance)) result.push_back(cast(imported)); } } @@ -6226,7 +6250,15 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( foundNameArities.insert(getArity(valueDecl)); for (auto base : cxxRecord->bases()) { - if (base.getAccessSpecifier() != clang::AccessSpecifier::AS_public) + clang::AccessSpecifier baseInheritance = base.getAccessSpecifier(); + // Skip this base class if this is a case of nested private inheritance. + // + // BUG: members of private base classes should actually be imported but + // inaccessible. Skipping them here may affect accurate overload + // resolution in cases of multiple inheritance (which is currently buggy + // anyway). + if (inheritance != clang::AS_none && + baseInheritance == clang::AS_private) continue; clang::QualType baseType = base.getType(); @@ -6242,12 +6274,19 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( if (cast(import)->getName() == name) continue; + if (inheritance != clang::AS_none) + // For nested inheritance, clamp inheritance to least permissive level + // which is the largest numerical value for clang::AccessSpecifier + baseInheritance = std::max(inheritance, baseInheritance); + static_assert(clang::AS_private > clang::AS_protected && + clang::AS_protected > clang::AS_public && + "using std::max() relies on this ordering"); + // Add Clang members that are imported lazily. auto baseResults = evaluateOrDefault( ctx.evaluator, - ClangRecordMemberLookup( - {cast(import), name, inheritingDecl}), - {}); + ClangRecordMemberLookup({cast(import), name, + inheritingDecl, baseInheritance}), {}); for (auto foundInBase : baseResults) { // Do not add duplicate entry with the same arity, @@ -7507,18 +7546,13 @@ Decl *ClangImporter::importDeclDirectly(const clang::NamedDecl *decl) { return Impl.importDecl(decl, Impl.CurrentVersion); } -ValueDecl *ClangImporter::Implementation::importBaseMemberDecl( - ValueDecl *decl, DeclContext *newContext) { - // Do not clone private C++ decls. - if (decl->getFormalAccess() < AccessLevel::Public) - return nullptr; - +ValueDecl *ClangImporter::Implementation::importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext, clang::AccessSpecifier inheritance) { // Make sure we don't clone the decl again for this class, as that would // result in multiple definitions of the same symbol. std::pair key = {decl, newContext}; auto known = clonedBaseMembers.find(key); if (known == clonedBaseMembers.end()) { - ValueDecl *cloned = cloneBaseMemberDecl(decl, newContext); + ValueDecl *cloned = cloneBaseMemberDecl(decl, newContext, inheritance); known = clonedBaseMembers.insert({key, cloned}).first; } @@ -7536,8 +7570,9 @@ size_t ClangImporter::Implementation::getImportedBaseMemberDeclArity( } ValueDecl *ClangImporter::importBaseMemberDecl(ValueDecl *decl, - DeclContext *newContext) { - return Impl.importBaseMemberDecl(decl, newContext); + DeclContext *newContext, + clang::AccessSpecifier inheritance) { + return Impl.importBaseMemberDecl(decl, newContext, inheritance); } void ClangImporter::diagnoseTopLevelValue(const DeclName &name) { diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 19bf1de082a7d..cea7743609266 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -4561,19 +4561,41 @@ namespace { if (!dc->isDerivedFrom(targetRecord)) return nullptr; + // Using Base::targetMethod() is only valid if targetMethod is protected + // or public, because otherwise it would not be inherited. + if (targetMethod->getAccess() == clang::AS_private) + return nullptr; + + // NOTE: it is possible that targetMethod is not actually accessible + // from the inheriting class, importedDC, because of private inheritance + // occuring somewhere along the class hierarchy. We rely on Clang to + // flag that as an error and assume targetMethod is accessible here. + // + // BUG: If the derived class already has a member with the same name, + // parameter list, and qualifications, the derived class member should + // hide or override (rather than conflict with) the member that is + // introduced from the base class. + auto importedBaseMethod = dyn_cast_or_null( Impl.importDecl(targetMethod, getActiveSwiftVersion())); - // This will be nullptr for a protected method of base class that is - // made public with a using declaration in a derived class. This is - // valid in C++ but we do not import such using declarations now. - // TODO: make this work for protected base methods. if (!importedBaseMethod) return nullptr; + + // It does not matter what access level we inherit targetMethod with + // because we will overwrite it according to the access specifier of + // the using decl. auto clonedMethod = dyn_cast_or_null( - Impl.importBaseMemberDecl(importedBaseMethod, importedDC)); + Impl.importBaseMemberDecl(importedBaseMethod, importedDC, + /*inheritance=*/clang::AS_public)); if (!clonedMethod) return nullptr; + // The clonedMethod's access level needs to be overwritten because it is + // possible for a using decl to promote a protected method into a public + // method, which importBaseMemberDecl() does not account for. + clonedMethod->overwriteAccess( + importer::convertClangAccess(decl->getAccess())); + bool success = processSpecialImportedFunc( clonedMethod, importedName, targetMethod->getOverloadedOperator()); if (!success) @@ -9918,7 +9940,8 @@ static void loadAllMembersOfSuperclassIfNeeded(ClassDecl *CD) { } void ClangImporter::Implementation::loadAllMembersOfRecordDecl( - NominalTypeDecl *swiftDecl, const clang::RecordDecl *clangRecord) { + NominalTypeDecl *swiftDecl, const clang::RecordDecl *clangRecord, + clang::AccessSpecifier inheritance) { // Import all of the members. llvm::SmallVector members; for (const clang::Decl *m : clangRecord->decls()) { @@ -9945,21 +9968,29 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl( } // Add the members here. - for (auto member: members) { - // This means we found a member in a C++ record's base class. - if (swiftDecl->getClangDecl() != clangRecord) { - auto namedMember = cast(member); - if (namedMember->getFormalAccess() < AccessLevel::Public) + for (auto member : members) { + if (inheritance != clang::AS_none) { + // This means we found a member in a C++ record's base class. + assert(swiftDecl->getClangDecl() != clangRecord); + auto baseMember = cast(member); + + // Skip this member if this is a case of nested private inheritance. + // + // BUG: private base class members should be inherited but inaccessible. + // Skipping them here may affect accurate overload resolution in cases of + // multiple inheritance (which is currently buggy anyway). + if (baseMember->getClangDecl()->getAccess() == clang::AS_private) continue; + // Do not clone the base member into the derived class // when the derived class already has a member of such // name and arity. auto memberArity = - getImportedBaseMemberDeclArity(namedMember); + getImportedBaseMemberDeclArity(baseMember); bool shouldAddBaseMember = true; for (const auto *currentMember : swiftDecl->getMembers()) { auto vd = dyn_cast(currentMember); - if (vd->getName() == namedMember->getName()) { + if (vd->getName() == baseMember->getName()) { if (memberArity == getImportedBaseMemberDeclArity(vd)) { shouldAddBaseMember = false; break; @@ -9968,10 +9999,11 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl( } if (!shouldAddBaseMember) continue; + // So we need to clone the member into the derived class. - if (auto newDecl = importBaseMemberDecl(namedMember, swiftDecl)) { - swiftDecl->addMember(newDecl); - } + if (auto cloned = importBaseMemberDecl(baseMember, swiftDecl, inheritance)) + swiftDecl->addMember(cloned); + continue; } @@ -9992,9 +10024,24 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl( // If this is a C++ record, look through the base classes too. if (auto cxxRecord = dyn_cast(clangRecord)) { for (auto base : cxxRecord->bases()) { - if (base.getAccessSpecifier() != clang::AccessSpecifier::AS_public) + clang::AccessSpecifier baseInheritance = base.getAccessSpecifier(); + // Skip this base class if this is a case of nested private inheritance. + // + // BUG: members of private base classes should actually be imported but + // inaccessible. Skipping them here may affect accurate overload + // resolution in cases of multiple inheritance (which is currently buggy + // anyway). + if (inheritance != clang::AS_none && baseInheritance == clang::AS_private) continue; + if (inheritance != clang::AS_none) + // For nested inheritance, clamp inheritance to least permissive level + // which is the largest numerical value for clang::AccessSpecifier + baseInheritance = std::max(inheritance, baseInheritance); + static_assert(clang::AS_private > clang::AS_protected && + clang::AS_protected > clang::AS_public && + "using std::max() relies on this ordering"); + clang::QualType baseType = base.getType(); if (auto spectType = dyn_cast(baseType)) baseType = spectType->desugar(); @@ -10004,7 +10051,7 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl( continue; auto *baseRecord = cast(baseType)->getDecl(); - loadAllMembersOfRecordDecl(swiftDecl, baseRecord); + loadAllMembersOfRecordDecl(swiftDecl, baseRecord, baseInheritance); } } } @@ -10045,7 +10092,8 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) { if (isa_and_nonnull(D->getClangDecl())) { loadAllMembersOfRecordDecl(cast(D), - cast(D->getClangDecl())); + cast(D->getClangDecl()), + /*inheritance=*/clang::AS_none); if (IDC) // Set member deserialization status IDC->setDeserializedMembers(true); return; diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 609992020e3e5..939bab49ba043 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -36,6 +36,7 @@ #include "swift/ClangImporter/ClangModule.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/Basic/Specifiers.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/Preprocessor.h" #include "clang/AST/DeclVisitor.h" @@ -690,7 +691,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation bool isDefaultArgSafeToImport(const clang::ParmVarDecl *param); - ValueDecl *importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext); + ValueDecl *importBaseMemberDecl(ValueDecl *decl, + DeclContext *newContext, + clang::AccessSpecifier inheritance); static size_t getImportedBaseMemberDeclArity(const ValueDecl *valueDecl); @@ -1636,7 +1639,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation loadAllMembersOfObjcContainer(Decl *D, const clang::ObjCContainerDecl *objcContainer); void loadAllMembersOfRecordDecl(NominalTypeDecl *swiftDecl, - const clang::RecordDecl *clangRecord); + const clang::RecordDecl *clangRecord, + clang::AccessSpecifier inheritance); void collectMembersToAdd(const clang::ObjCContainerDecl *objcContainer, Decl *D, DeclContext *DC, diff --git a/test/Interop/Cxx/class/inheritance/functions-module-interface.swift b/test/Interop/Cxx/class/inheritance/functions-module-interface.swift index ada30f303b75b..5aa57d2ae7e63 100644 --- a/test/Interop/Cxx/class/inheritance/functions-module-interface.swift +++ b/test/Interop/Cxx/class/inheritance/functions-module-interface.swift @@ -1,123 +1,227 @@ -// RUN: %target-swift-ide-test -print-module -print-implicit-attrs -module-to-print=Functions -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s +// RUN: %target-swift-ide-test -print-module -print-implicit-attrs -print-access -module-to-print=Functions -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s -// CHECK: struct NonTrivial { -// CHECK-NEXT: init() +// CHECK: public struct NonTrivial { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inNonTrivial() -> UnsafePointer! +// CHECK-NEXT: public func inNonTrivial() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inNonTrivialWithArgs(_ a: Int32, _ b: Int32) -> UnsafePointer! +// CHECK-NEXT: public func inNonTrivialWithArgs(_ a: Int32, _ b: Int32) -> UnsafePointer! // CHECK-NEXT: } -// CHECK-NEXT: struct Base { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct Base { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func mutatingInBase() -> UnsafePointer! +// CHECK-NEXT: public mutating func mutatingInBase() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func constInBase() -> UnsafePointer! +// CHECK-NEXT: public func constInBase() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func rvalueThisInBase() -> UnsafePointer! +// CHECK-NEXT: public func rvalueThisInBase() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer! +// CHECK-NEXT: public func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer! +// CHECK-NEXT: public func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func returnsNonTrivialInBase() -> NonTrivial +// CHECK-NEXT: public func returnsNonTrivialInBase() -> NonTrivial // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func templateInBase(_ t: T) -> UnsafePointer! +// CHECK-NEXT: public func templateInBase(_ t: T) -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: static func staticInBase() -> UnsafePointer! +// CHECK-NEXT: public static func staticInBase() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func swiftRenamed(input i: Int32) -> Int32 +// CHECK-NEXT: public mutating func swiftRenamed(input i: Int32) -> Int32 // CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "swiftRenamed(input:)") -// CHECK-NEXT: mutating func renamed(_ i: Int32) -> Int32 +// CHECK-NEXT: public mutating func renamed(_ i: Int32) -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: @_effects(readonly) func pure() -> Int32 +// CHECK-NEXT: @_effects(readonly) public func pure() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodNameSameSignature() -> Int32 +// CHECK-NEXT: public func sameMethodNameSameSignature() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodDifferentSignature() -> Int32 +// CHECK-NEXT: public func sameMethodDifferentSignature() -> Int32 // CHECK-NEXT: } -// CHECK-NEXT: struct OtherBase { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct OtherBase { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inOtherBase() -> UnsafePointer! +// CHECK-NEXT: public func inOtherBase() -> UnsafePointer! // CHECK-NEXT: } -// CHECK-NEXT: struct Derived { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct Derived { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inDerived() -> UnsafePointer! +// CHECK-NEXT: public func inDerived() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodNameSameSignature() -> Int32 +// CHECK-NEXT: public func sameMethodNameSameSignature() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodDifferentSignature(_ x: Int32) -> Int32 +// CHECK-NEXT: public func sameMethodDifferentSignature(_ x: Int32) -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func mutatingInBase() -> UnsafePointer? +// CHECK-NEXT: public mutating func mutatingInBase() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func constInBase() -> UnsafePointer? +// CHECK-NEXT: public func constInBase() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? +// CHECK-NEXT: public func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? +// CHECK-NEXT: public func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func returnsNonTrivialInBase() -> NonTrivial +// CHECK-NEXT: public func returnsNonTrivialInBase() -> NonTrivial // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func swiftRenamed(input i: Int32) -> Int32 +// CHECK-NEXT: public mutating func swiftRenamed(input i: Int32) -> Int32 // CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "swiftRenamed(input:)") -// CHECK-NEXT: mutating func renamed(_ i: Int32) -> Int32 +// CHECK-NEXT: public mutating func renamed(_ i: Int32) -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: @_effects(readonly) func pure() -> Int32 +// CHECK-NEXT: @_effects(readonly) public func pure() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodDifferentSignature() -> Int32 +// CHECK-NEXT: public func sameMethodDifferentSignature() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inOtherBase() -> UnsafePointer? +// CHECK-NEXT: public func inOtherBase() -> UnsafePointer? // CHECK-NEXT: } -// CHECK-NEXT: struct DerivedFromDerived { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct DerivedFromDerived { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func topLevel() -> UnsafePointer! +// CHECK-NEXT: public func topLevel() -> UnsafePointer! // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inDerived() -> UnsafePointer? +// CHECK-NEXT: public func inDerived() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodNameSameSignature() -> Int32 +// CHECK-NEXT: public func sameMethodNameSameSignature() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodDifferentSignature(_ x: Int32) -> Int32 +// CHECK-NEXT: public func sameMethodDifferentSignature(_ x: Int32) -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func mutatingInBase() -> UnsafePointer? +// CHECK-NEXT: public mutating func mutatingInBase() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func constInBase() -> UnsafePointer? +// CHECK-NEXT: public func constInBase() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? +// CHECK-NEXT: public func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? +// CHECK-NEXT: public func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func returnsNonTrivialInBase() -> NonTrivial +// CHECK-NEXT: public func returnsNonTrivialInBase() -> NonTrivial // CHECK-NEXT: @discardableResult -// CHECK-NEXT: mutating func swiftRenamed(input i: Int32) -> Int32 +// CHECK-NEXT: public mutating func swiftRenamed(input i: Int32) -> Int32 // CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "swiftRenamed(input:)") -// CHECK-NEXT: mutating func renamed(_ i: Int32) -> Int32 +// CHECK-NEXT: public mutating func renamed(_ i: Int32) -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: @_effects(readonly) func pure() -> Int32 +// CHECK-NEXT: @_effects(readonly) public func pure() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func sameMethodDifferentSignature() -> Int32 +// CHECK-NEXT: public func sameMethodDifferentSignature() -> Int32 // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inOtherBase() -> UnsafePointer? +// CHECK-NEXT: public func inOtherBase() -> UnsafePointer? // CHECK-NEXT: } -// CHECK-NEXT: struct DerivedFromNonTrivial { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct DerivedFromNonTrivial { +// CHECK-NEXT: public init() // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inNonTrivial() -> UnsafePointer? +// CHECK-NEXT: public func inNonTrivial() -> UnsafePointer? // CHECK-NEXT: @discardableResult -// CHECK-NEXT: func inNonTrivialWithArgs(_ a: Int32, _ b: Int32) -> UnsafePointer? +// CHECK-NEXT: public func inNonTrivialWithArgs(_ a: Int32, _ b: Int32) -> UnsafePointer? // CHECK-NEXT: } -// CHECK-NEXT: struct PrivatelyInherited { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct PrivatelyInherited { +// CHECK-NEXT: public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private mutating func mutatingInBase() -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func constInBase() -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func returnsNonTrivialInBase() -> NonTrivial +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private mutating func swiftRenamed(input i: Int32) -> Int32 +// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "swiftRenamed(input:)") +// CHECK-NEXT: private mutating func renamed(_ i: Int32) -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: @_effects(readonly) private func pure() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func sameMethodNameSameSignature() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func sameMethodDifferentSignature() -> Int32 // CHECK-NEXT: } -// CHECK-NEXT: struct ProtectedInherited { -// CHECK-NEXT: init() +// CHECK-NEXT: public struct ProtectedInherited { +// CHECK-NEXT: public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private mutating func mutatingInBase() -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func constInBase() -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func takesArgsInBase(_ a: Int32, _ b: Int32, _ c: Int32) -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func takesNonTrivialInBase(_ a: NonTrivial) -> UnsafePointer? +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func returnsNonTrivialInBase() -> NonTrivial +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private mutating func swiftRenamed(input i: Int32) -> Int32 +// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "swiftRenamed(input:)") +// CHECK-NEXT: private mutating func renamed(_ i: Int32) -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: @_effects(readonly) private func pure() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func sameMethodNameSameSignature() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: private func sameMethodDifferentSignature() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct EmptyBaseClass { +// CHECK-NEXT: public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func inBase() -> UnsafePointer! // CHECK-NEXT: } + +// CHECK-NEXT: public struct DerivedFromEmptyBaseClass { +// CHECK-NEXT: public init() +// CHECK-NEXT: public var a: Int32 +// CHECK-NEXT: public var b: Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func inBase() -> UnsafePointer? +// CHECK-NEXT: } + +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getCopyCounter() -> UnsafeMutablePointer +// CHECK-NEXT: public struct CopyTrackedBaseClass { +// CHECK-NEXT: public init(_ x: Int32) +// CHECK-NEXT: @available(*, deprecated, message: "This zero-initializes the backing memory of the struct, which is unsafe for some C++ structs. Consider adding an explicit default initializer for this C++ struct.") +// CHECK-NEXT: @_transparent public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getX() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public mutating func getXMut() -> Int32 +// CHECK-NEXT: private var x: Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct CopyTrackedDerivedClass { +// CHECK-NEXT: public init(_ x: Int32) +// CHECK-NEXT: @available(*, deprecated, message: "This zero-initializes the backing memory of the struct, which is unsafe for some C++ structs. Consider adding an explicit default initializer for this C++ struct.") +// CHECK-NEXT: @_transparent public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getDerivedX() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getX() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public mutating func getXMut() -> Int32 +// CHECK-NEXT: private var x: Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct NonEmptyBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getY() -> Int32 +// CHECK-NEXT: private var y: Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct CopyTrackedDerivedDerivedClass { +// CHECK-NEXT: public init(_ x: Int32) +// CHECK-NEXT: @available(*, deprecated, message: "This zero-initializes the backing memory of the struct, which is unsafe for some C++ structs. Consider adding an explicit default initializer for this C++ struct.") +// CHECK-NEXT: @_transparent public init() +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getY() -> Int32 +// CHECK-NEXT: private var y: Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getDerivedX() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public func getX() -> Int32 +// CHECK-NEXT: @discardableResult +// CHECK-NEXT: public mutating func getXMut() -> Int32 +// CHECK-NEXT: private var x: Int32 +// CHECK-NEXT: } + diff --git a/test/Interop/Cxx/class/inheritance/using-base-members-typechecker.swift b/test/Interop/Cxx/class/inheritance/using-base-members-typechecker.swift index 5e54f502c44ce..0430430d8919c 100644 --- a/test/Interop/Cxx/class/inheritance/using-base-members-typechecker.swift +++ b/test/Interop/Cxx/class/inheritance/using-base-members-typechecker.swift @@ -6,11 +6,11 @@ import UsingBaseMembers let a = PublicBasePrivateInheritance() let _ = a.publicGetter() -a.notExposed() // expected-error {{value of type 'PublicBasePrivateInheritance' has no member 'notExposed'}} +a.notExposed() // expected-error {{'notExposed' is inaccessible due to 'private' protection level}} let b = PublicBaseProtectedInheritance() let _ = b.publicGetter() -b.notExposed() // expected-error {{value of type 'PublicBaseProtectedInheritance' has no member 'notExposed'}} +b.notExposed() // expected-error {{'notExposed' is inaccessible due to 'private' protection level}} let _ = UsingBaseConstructorWithParam(566 as Int32) let _ = UsingBaseConstructorWithParam(566 as UInt32) From 1f853c0fb2f0830c4be0f939d88e4d3282628812 Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 1 Feb 2025 17:02:21 -0800 Subject: [PATCH 2/3] Add tests for importing non-public inherited members --- .../Cxx/class/access/Inputs/module.modulemap | 4 + .../access/Inputs/non-public-inheritance.h | 37 ++++ .../non-public-inheritance-executable.swift | 120 ++++++++++++ ...-public-inheritance-module-interface.swift | 75 ++++++++ .../non-public-inheritance-typecheck.swift | 182 ++++++++++++++++++ 5 files changed, 418 insertions(+) create mode 100644 test/Interop/Cxx/class/access/Inputs/module.modulemap create mode 100644 test/Interop/Cxx/class/access/Inputs/non-public-inheritance.h create mode 100644 test/Interop/Cxx/class/access/non-public-inheritance-executable.swift create mode 100644 test/Interop/Cxx/class/access/non-public-inheritance-module-interface.swift create mode 100644 test/Interop/Cxx/class/access/non-public-inheritance-typecheck.swift diff --git a/test/Interop/Cxx/class/access/Inputs/module.modulemap b/test/Interop/Cxx/class/access/Inputs/module.modulemap new file mode 100644 index 0000000000000..ec9f6b1d07a40 --- /dev/null +++ b/test/Interop/Cxx/class/access/Inputs/module.modulemap @@ -0,0 +1,4 @@ +module NonPublicInheritance { + requires cplusplus + header "non-public-inheritance.h" +} diff --git a/test/Interop/Cxx/class/access/Inputs/non-public-inheritance.h b/test/Interop/Cxx/class/access/Inputs/non-public-inheritance.h new file mode 100644 index 0000000000000..6f30404208370 --- /dev/null +++ b/test/Interop/Cxx/class/access/Inputs/non-public-inheritance.h @@ -0,0 +1,37 @@ +#ifndef NON_PUBLIC_INHERTIANCE_H +#define NON_PUBLIC_INHERTIANCE_H + +// TODO: dependent on private_fileid feature +// #define BLESS __attribute__((__swift_attr__("private_fileid:main/blessed.swift"))) +#define BLESS + +const int PUBL_RETURN_VAL = 1; +const int PROT_RETURN_VAL = 2; +const int PRIV_RETURN_VAL = 3; + +class BLESS Base { +public: + int publ(void) const { return PUBL_RETURN_VAL; } +protected: + int prot(void) const { return PROT_RETURN_VAL; } +private: + int priv(void) const { return PRIV_RETURN_VAL; } +}; + +class BLESS PublBase : public Base {}; +class BLESS ProtBase : protected Base {}; +class BLESS PrivBase : private Base {}; + +class BLESS PublPublBase : public PublBase {}; +class BLESS ProtPublBase : protected PublBase {}; +class BLESS PrivPublBase : private PublBase {}; + +class BLESS PublProtBase : public ProtBase {}; +class BLESS ProtProtBase : protected ProtBase {}; +class BLESS PrivProtBase : private ProtBase {}; + +class BLESS PublPrivBase : public PrivBase {}; +class BLESS ProtPrivBase : protected PrivBase {}; +class BLESS PrivPrivBase : private PrivBase {}; + +#endif /* NON_PUBLIC_INHERTIANCE_H */ diff --git a/test/Interop/Cxx/class/access/non-public-inheritance-executable.swift b/test/Interop/Cxx/class/access/non-public-inheritance-executable.swift new file mode 100644 index 0000000000000..d0a253df747e9 --- /dev/null +++ b/test/Interop/Cxx/class/access/non-public-inheritance-executable.swift @@ -0,0 +1,120 @@ +// Test that all accessible inherited methods can be called. +// +// Note that this test isn't very meaningful until we are allowed to access +// non-public members (those are the TODOs). It is checked in for now as +// a placeholder (and it _does_ at least ensure that things haven't gone +// horribly wrong). +// +// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -cxx-interoperability-mode=default) +// +// REQUIRES: executable_test + +import StdlibUnittest +import NonPublicInheritance + +var Tests = TestSuite("NonPublicInheritance") +Tests.test("Base") { Base().ext() } +Tests.test("PublBase") { PublBase().ext() } +Tests.test("ProtBase") { ProtBase().ext() } +Tests.test("PrivBase") { PrivBase().ext() } +Tests.test("PublPublBase") { PublPublBase().ext() } +Tests.test("ProtPublBase") { ProtPublBase().ext() } +Tests.test("PrivPublBase") { PrivPublBase().ext() } +Tests.test("PublProtBase") { PublProtBase().ext() } +Tests.test("ProtProtBase") { ProtProtBase().ext() } +Tests.test("PrivProtBase") { PrivProtBase().ext() } +Tests.test("PublPrivBase") { PublPrivBase().ext() } +Tests.test("ProtPrivBase") { ProtPrivBase().ext() } +Tests.test("PrivPrivBase") { PrivPrivBase().ext() } + +extension Base { + func ext() { + expectEqual(publ(), PUBL_RETURN_VAL) + // TODO: prot() + // TODO: priv() + } +} + +extension PublBase { + func ext() { + expectEqual(publ(), PUBL_RETURN_VAL) + // TODO: prot() + } +} + +extension PublPublBase { + func ext() { + expectEqual(publ(), PUBL_RETURN_VAL) + // TODO: prot() + } +} + +extension ProtPublBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension PrivPublBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension ProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + + +extension PublProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension ProtProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension PrivProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension PrivBase { + func ext() { + // TODO: publ() + // TODO: prot() + } +} + +extension PublPrivBase { + func ext() { + // Nothing to test (nothing is accessible) + } +} + +extension ProtPrivBase { + func ext() { + // Nothing to test (nothing is accessible) + } +} + +extension PrivPrivBase { + func ext() { + // Nothing to test (nothing is accessible) + } +} + +runAllTests() diff --git a/test/Interop/Cxx/class/access/non-public-inheritance-module-interface.swift b/test/Interop/Cxx/class/access/non-public-inheritance-module-interface.swift new file mode 100644 index 0000000000000..e2383dd87b093 --- /dev/null +++ b/test/Interop/Cxx/class/access/non-public-inheritance-module-interface.swift @@ -0,0 +1,75 @@ +// RUN: %target-swift-ide-test -print-module -module-to-print=NonPublicInheritance -print-access -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s + +// CHECK: public struct Base { +// CHECK-NEXT: public init() +// CHECK-NEXT: public func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: private func priv() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PublBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: public func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct ProtBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PrivBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PublPublBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: public func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct ProtPublBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PrivPublBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PublProtBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct ProtProtBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PrivProtBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: private func publ() -> Int32 +// CHECK-NEXT: private func prot() -> Int32 +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PublPrivBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: } + +// CHECK-NEXT: public struct ProtPrivBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: } + +// CHECK-NEXT: public struct PrivPrivBase { +// CHECK-NEXT: public init() +// CHECK-NEXT: } + diff --git a/test/Interop/Cxx/class/access/non-public-inheritance-typecheck.swift b/test/Interop/Cxx/class/access/non-public-inheritance-typecheck.swift new file mode 100644 index 0000000000000..35481d5f19628 --- /dev/null +++ b/test/Interop/Cxx/class/access/non-public-inheritance-typecheck.swift @@ -0,0 +1,182 @@ +//--- blessed.swift +// RUN: split-file %s %t +// RUN: %target-swift-frontend -typecheck -verify -I %S/Inputs -cxx-interoperability-mode=default -module-name main %t/blessed.swift + +import NonPublicInheritance + +// Extensions of each class test whether we correctly modeled *which* members +// get inherited, but do not tell us whether they were inherited with the right +// access permissions. +// +// Global functions (i.e., not in extensions) tell us whether those members were +// inherited with the correct Swift access level (i.e., public vs private). + +extension Base { + func ext() { + publ() + // TODO: prot() + // TODO: priv() + } +} +func fBase(v: Base) { + v.publ() + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{'priv' is inaccessible due to 'private' protection level}} +} + +extension PublBase { + func ext() { + publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPublBase(v: PublBase) { + v.publ() + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PublBase' has no member 'priv'}} +} + +extension PublPublBase { + func ext() { + publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPublPublBase(v: PublPublBase) { + v.publ() + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PublPublBase' has no member 'priv'}} +} + +extension ProtPublBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fProtPublBase(v: ProtPublBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'ProtPublBase' has no member 'priv'}} +} + +extension PrivPublBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPrivPublBase(v: PrivPublBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PrivPublBase' has no member 'priv'}} +} + +extension ProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fProtBase(v: ProtBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'ProtBase' has no member 'priv'}} +} + + +extension PublProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPublProtBase(v: PublProtBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PublProtBase' has no member 'priv'}} +} + +extension ProtProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fProtProtBase(v: ProtProtBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'ProtProtBase' has no member 'priv'}} +} + +extension PrivProtBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPrivProtBase(v: PrivProtBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PrivProtBase' has no member 'priv'}} +} + +extension PrivBase { + func ext() { + // TODO: publ() + // TODO: prot() + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPrivBase(v: PrivBase) { + v.publ() // expected-error {{'publ' is inaccessible due to 'private' protection level}} + v.prot() // expected-error {{'prot' is inaccessible due to 'private' protection level}} + v.priv() // expected-error {{value of type 'PrivBase' has no member 'priv'}} +} + +extension PublPrivBase { + func ext() { + publ() // expected-error {{cannot find 'publ' in scope}} + prot() // expected-error {{cannot find 'prot' in scope}} + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPublPrivBase(v: PublPrivBase) { + v.publ() // expected-error {{value of type 'PublPrivBase' has no member 'publ'}} + v.prot() // expected-error {{value of type 'PublPrivBase' has no member 'prot'}} + v.priv() // expected-error {{value of type 'PublPrivBase' has no member 'priv'}} +} + +extension ProtPrivBase { + func ext() { + publ() // expected-error {{cannot find 'publ' in scope}} + prot() // expected-error {{cannot find 'prot' in scope}} + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fProtPrivBase(v: ProtPrivBase) { + v.publ() // expected-error {{value of type 'ProtPrivBase' has no member 'publ'}} + v.prot() // expected-error {{value of type 'ProtPrivBase' has no member 'prot'}} + v.priv() // expected-error {{value of type 'ProtPrivBase' has no member 'priv'}} +} + +extension PrivPrivBase { + func ext() { + publ() // expected-error {{cannot find 'publ' in scope}} + prot() // expected-error {{cannot find 'prot' in scope}} + priv() // expected-error {{cannot find 'priv' in scope}} + } +} +func fPrivPrivBase(v: PrivPrivBase) { + v.publ() // expected-error {{value of type 'PrivPrivBase' has no member 'publ'}} + v.prot() // expected-error {{value of type 'PrivPrivBase' has no member 'prot'}} + v.priv() // expected-error {{value of type 'PrivPrivBase' has no member 'priv'}} +} From 8a2dd808feea08a6b426a05d97cc7778ac0ad5ec Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 1 Feb 2025 17:03:25 -0800 Subject: [PATCH 3/3] Fix latent bug that causes spurious ambiguous member lookups This should have been fixed in #78673 but it was missed because it requires a fairly complex trigger. The fix is to ensure that we never try to clone inherited methods that are themselves inherited. --- lib/ClangImporter/ClangImporter.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 19708323a8b7a..def084c95dbbe 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -6223,6 +6223,11 @@ TinyPtrVector ClangRecordMemberLookup::evaluate( auto memberClangDecl = namedMember->getClangDecl(); + // Skip this base class member if it isn't from recordDecl; this happens + // when this member was inherited. + if (memberClangDecl != recordDecl->getClangDecl()) + continue; + // Skip this base class if this is a case of nested private inheritance. // // BUG: private base class members should be inherited but inaccessible.