Skip to content

Commit e583acf

Browse files
committed
[cxx-interop] Import using decls that expose methods from private base classes
If a C++ type `Derived` inherits from `Base` privately, the public methods from `Base` should not be callable on an instance of `Derived`. However, C++ supports exposing such methods via a using declaration: `using MyPrivateBase::myPublicMethod;`. MSVC started using this feature for `std::optional` which means Swift doesn't correctly import `var pointee: Pointee` for instantiations of `std::optional` on Windows. This prevents the automatic conformance to `CxxOptional` from being synthesized. rdar://114282353 / resolves #68068
1 parent c1e8924 commit e583acf

14 files changed

+264
-77
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5476,6 +5476,13 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) {
54765476
(fn->getClangDecl() &&
54775477
isa<clang::FunctionTemplateDecl>(fn->getClangDecl())))
54785478
return nullptr;
5479+
if (auto cxxMethod =
5480+
dyn_cast_or_null<clang::CXXMethodDecl>(fn->getClangDecl())) {
5481+
// FIXME: if this function has rvalue this, we won't be able to synthesize
5482+
// the accessor correctly.
5483+
if (cxxMethod->getRefQualifier() == clang::RefQualifierKind::RQ_RValue)
5484+
return nullptr;
5485+
}
54795486

54805487
ASTContext &context = decl->getASTContext();
54815488
auto out = FuncDecl::createImplicit(

lib/ClangImporter/ImportDecl.cpp

Lines changed: 130 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,6 +3237,58 @@ namespace {
32373237
llvm::None);
32383238
}
32393239

3240+
/// Handles special functions such as subscripts and dereference operators.
3241+
bool processSpecialImportedFunc(FuncDecl *func, ImportedName importedName) {
3242+
auto dc = func->getDeclContext();
3243+
3244+
if (importedName.isSubscriptAccessor()) {
3245+
assert(func->getParameters()->size() == 1);
3246+
auto typeDecl = dc->getSelfNominalTypeDecl();
3247+
auto parameter = func->getParameters()->get(0);
3248+
auto parameterType = parameter->getTypeInContext();
3249+
if (!typeDecl || !parameterType)
3250+
return false;
3251+
if (parameter->isInOut())
3252+
// Subscripts with inout parameters are not allowed in Swift.
3253+
return false;
3254+
3255+
auto &getterAndSetter = Impl.cxxSubscripts[{typeDecl, parameterType}];
3256+
3257+
switch (importedName.getAccessorKind()) {
3258+
case ImportedAccessorKind::SubscriptGetter:
3259+
getterAndSetter.first = func;
3260+
break;
3261+
case ImportedAccessorKind::SubscriptSetter:
3262+
getterAndSetter.second = func;
3263+
break;
3264+
default:
3265+
llvm_unreachable("invalid subscript kind");
3266+
}
3267+
3268+
Impl.markUnavailable(func, "use subscript");
3269+
}
3270+
3271+
if (importedName.isDereferenceAccessor()) {
3272+
auto typeDecl = dc->getSelfNominalTypeDecl();
3273+
auto &getterAndSetter = Impl.cxxDereferenceOperators[typeDecl];
3274+
3275+
switch (importedName.getAccessorKind()) {
3276+
case ImportedAccessorKind::DereferenceGetter:
3277+
getterAndSetter.first = func;
3278+
break;
3279+
case ImportedAccessorKind::DereferenceSetter:
3280+
getterAndSetter.second = func;
3281+
break;
3282+
default:
3283+
llvm_unreachable("invalid dereference operator kind");
3284+
}
3285+
3286+
Impl.markUnavailable(func, "use .pointee property");
3287+
}
3288+
3289+
return true;
3290+
}
3291+
32403292
Decl *importFunctionDecl(
32413293
const clang::FunctionDecl *decl, ImportedName importedName,
32423294
llvm::Optional<ImportedName> correctSwiftName,
@@ -3556,62 +3608,14 @@ namespace {
35563608
func->setImportAsStaticMember();
35573609
}
35583610
}
3611+
// Someday, maybe this will need to be 'open' for C++ virtual methods.
3612+
func->setAccess(AccessLevel::Public);
35593613

3560-
bool makePrivate = false;
3561-
3562-
if (importedName.isSubscriptAccessor() && !importFuncWithoutSignature) {
3563-
assert(func->getParameters()->size() == 1);
3564-
auto typeDecl = dc->getSelfNominalTypeDecl();
3565-
auto parameter = func->getParameters()->get(0);
3566-
auto parameterType = parameter->getTypeInContext();
3567-
if (!typeDecl || !parameterType)
3614+
if (!importFuncWithoutSignature) {
3615+
bool success = processSpecialImportedFunc(func, importedName);
3616+
if (!success)
35683617
return nullptr;
3569-
if (parameter->isInOut())
3570-
// Subscripts with inout parameters are not allowed in Swift.
3571-
return nullptr;
3572-
3573-
auto &getterAndSetter = Impl.cxxSubscripts[{ typeDecl,
3574-
parameterType }];
3575-
3576-
switch (importedName.getAccessorKind()) {
3577-
case ImportedAccessorKind::SubscriptGetter:
3578-
getterAndSetter.first = func;
3579-
break;
3580-
case ImportedAccessorKind::SubscriptSetter:
3581-
getterAndSetter.second = func;
3582-
break;
3583-
default:
3584-
llvm_unreachable("invalid subscript kind");
3585-
}
3586-
3587-
Impl.markUnavailable(func, "use subscript");
3588-
}
3589-
3590-
if (importedName.isDereferenceAccessor() &&
3591-
!importFuncWithoutSignature) {
3592-
auto typeDecl = dc->getSelfNominalTypeDecl();
3593-
auto &getterAndSetter = Impl.cxxDereferenceOperators[typeDecl];
3594-
3595-
switch (importedName.getAccessorKind()) {
3596-
case ImportedAccessorKind::DereferenceGetter:
3597-
getterAndSetter.first = func;
3598-
break;
3599-
case ImportedAccessorKind::DereferenceSetter:
3600-
getterAndSetter.second = func;
3601-
break;
3602-
default:
3603-
llvm_unreachable("invalid dereference operator kind");
3604-
}
3605-
3606-
Impl.markUnavailable(func, "use .pointee property");
3607-
makePrivate = true;
36083618
}
3609-
3610-
if (makePrivate)
3611-
func->setAccess(AccessLevel::Private);
3612-
else
3613-
// Someday, maybe this will need to be 'open' for C++ virtual methods.
3614-
func->setAccess(AccessLevel::Public);
36153619
}
36163620

36173621
result->setIsObjC(false);
@@ -3924,18 +3928,27 @@ namespace {
39243928
}
39253929

39263930
Decl *VisitUsingDecl(const clang::UsingDecl *decl) {
3927-
// Using declarations are not imported.
3931+
// See VisitUsingShadowDecl below.
39283932
return nullptr;
39293933
}
39303934

39313935
Decl *VisitUsingShadowDecl(const clang::UsingShadowDecl *decl) {
3932-
// Only import types for now.
3933-
if (!isa<clang::TypeDecl>(decl->getUnderlyingDecl()))
3936+
// Only import:
3937+
// 1. Types
3938+
// 2. C++ methods from privately inherited base classes
3939+
if (!isa<clang::TypeDecl>(decl->getTargetDecl()) &&
3940+
!isa<clang::CXXMethodDecl>(decl->getTargetDecl()))
3941+
return nullptr;
3942+
// We don't import `using BaseClass::BaseClass` for now.
3943+
if (isa<clang::CXXConstructorDecl>(decl->getTargetDecl()))
39343944
return nullptr;
39353945

39363946
ImportedName importedName;
39373947
llvm::Optional<ImportedName> correctSwiftName;
39383948
std::tie(importedName, correctSwiftName) = importFullName(decl);
3949+
// Don't import something that doesn't have a name.
3950+
if (importedName.getDeclName().isSpecial())
3951+
return nullptr;
39393952
auto Name = importedName.getDeclName().getBaseIdentifier();
39403953
if (Name.empty())
39413954
return nullptr;
@@ -3946,30 +3959,66 @@ namespace {
39463959
return importCompatibilityTypeAlias(decl, importedName,
39473960
*correctSwiftName);
39483961

3949-
auto DC =
3962+
auto importedDC =
39503963
Impl.importDeclContextOf(decl, importedName.getEffectiveContext());
3951-
if (!DC)
3964+
if (!importedDC)
39523965
return nullptr;
39533966

3954-
Decl *SwiftDecl = Impl.importDecl(decl->getUnderlyingDecl(), getActiveSwiftVersion());
3955-
if (!SwiftDecl)
3956-
return nullptr;
3967+
if (isa<clang::TypeDecl>(decl->getTargetDecl())) {
3968+
Decl *SwiftDecl = Impl.importDecl(decl->getUnderlyingDecl(), getActiveSwiftVersion());
3969+
if (!SwiftDecl)
3970+
return nullptr;
39573971

3958-
const TypeDecl *SwiftTypeDecl = dyn_cast<TypeDecl>(SwiftDecl);
3959-
if (!SwiftTypeDecl)
3960-
return nullptr;
3972+
const TypeDecl *SwiftTypeDecl = dyn_cast<TypeDecl>(SwiftDecl);
3973+
if (!SwiftTypeDecl)
3974+
return nullptr;
39613975

3962-
auto Loc = Impl.importSourceLoc(decl->getLocation());
3963-
auto Result = Impl.createDeclWithClangNode<TypeAliasDecl>(
3964-
decl,
3965-
AccessLevel::Public,
3966-
Impl.importSourceLoc(decl->getBeginLoc()),
3967-
SourceLoc(), Name,
3968-
Loc,
3969-
/*genericparams*/nullptr, DC);
3970-
Result->setUnderlyingType(SwiftTypeDecl->getDeclaredInterfaceType());
3976+
auto Loc = Impl.importSourceLoc(decl->getLocation());
3977+
auto Result = Impl.createDeclWithClangNode<TypeAliasDecl>(
3978+
decl,
3979+
AccessLevel::Public,
3980+
Impl.importSourceLoc(decl->getBeginLoc()),
3981+
SourceLoc(), Name,
3982+
Loc,
3983+
/*genericparams*/nullptr, importedDC);
3984+
Result->setUnderlyingType(SwiftTypeDecl->getDeclaredInterfaceType());
3985+
3986+
return Result;
3987+
}
3988+
if (auto targetMethod =
3989+
dyn_cast<clang::CXXMethodDecl>(decl->getTargetDecl())) {
3990+
auto dc = dyn_cast<clang::CXXRecordDecl>(decl->getDeclContext());
3991+
3992+
auto targetDC = targetMethod->getDeclContext();
3993+
auto targetRecord = dyn_cast<clang::CXXRecordDecl>(targetDC);
3994+
if (!targetRecord)
3995+
return nullptr;
39713996

3972-
return Result;
3997+
// If this struct is not inherited from the struct where the method is
3998+
// defined, bail.
3999+
if (!dc->isDerivedFrom(targetRecord))
4000+
return nullptr;
4001+
4002+
auto importedBaseMethod = dyn_cast_or_null<FuncDecl>(
4003+
Impl.importDecl(targetMethod, getActiveSwiftVersion()));
4004+
// This will be nullptr for a protected method of base class that is
4005+
// made public with a using declaration in a derived class. This is
4006+
// valid in C++ but we do not import such using declarations now.
4007+
// TODO: make this work for protected base methods.
4008+
if (!importedBaseMethod)
4009+
return nullptr;
4010+
auto clonedMethod = dyn_cast_or_null<FuncDecl>(
4011+
Impl.importBaseMemberDecl(importedBaseMethod, importedDC));
4012+
if (!clonedMethod)
4013+
return nullptr;
4014+
4015+
bool success = processSpecialImportedFunc(clonedMethod, importedName);
4016+
if (!success)
4017+
return nullptr;
4018+
4019+
return clonedMethod;
4020+
}
4021+
return nullptr;
39734022
}
39744023

39754024
/// Add an @objc(name) attribute with the given, optional name expressed as
@@ -8353,11 +8402,17 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
83538402
if (Result &&
83548403
(!Result->getDeclContext()->isModuleScopeContext() ||
83558404
isa<ClangModuleUnit>(Result->getDeclContext()))) {
8405+
// For using declarations that expose a method of a base class, the Clang
8406+
// decl is synthesized lazily when the method is actually used from Swift.
8407+
bool hasSynthesizedClangNode =
8408+
isa<clang::UsingShadowDecl>(ClangDecl) && isa<FuncDecl>(Result);
8409+
83568410
// Either the Swift declaration was from stdlib,
83578411
// or we imported the underlying decl of the typedef,
83588412
// or we imported the decl itself.
83598413
bool ImportedCorrectly =
83608414
!Result->getClangDecl() || SkippedOverTypedef ||
8415+
hasSynthesizedClangNode ||
83618416
Result->getClangDecl()->getCanonicalDecl() == Canon;
83628417

83638418
// Or the other type is a typedef,
@@ -8380,7 +8435,7 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
83808435
}
83818436
assert(ImportedCorrectly);
83828437
}
8383-
assert(Result->hasClangNode());
8438+
assert(Result->hasClangNode() || hasSynthesizedClangNode);
83848439
}
83858440
#else
83868441
(void)SkippedOverTypedef;

lib/ClangImporter/ImportName.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,17 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
15221522
return ImportedName();
15231523
result.effectiveContext = effectiveCtx;
15241524

1525+
// If this is a using declaration, import the name of the shadowed decl and
1526+
// adjust the context.
1527+
if (auto usingShadowDecl = dyn_cast<clang::UsingShadowDecl>(D)) {
1528+
auto targetDecl = usingShadowDecl->getTargetDecl();
1529+
if (isa<clang::CXXMethodDecl>(targetDecl)) {
1530+
ImportedName baseName = importName(targetDecl, version, givenName);
1531+
baseName.effectiveContext = effectiveCtx;
1532+
return baseName;
1533+
}
1534+
}
1535+
15251536
// Gather information from the swift_async attribute, if there is one.
15261537
llvm::Optional<unsigned> completionHandlerParamIndex;
15271538
bool completionHandlerFlagIsZeroOnError = false;

lib/ClangImporter/ImporterImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,9 +663,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
663663
llvm::DenseMap<std::pair<ValueDecl *, DeclContext *>, ValueDecl *>
664664
clonedBaseMembers;
665665

666+
public:
666667
ValueDecl *importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext);
667668

668-
public:
669669
static size_t getImportedBaseMemberDeclArity(const ValueDecl *valueDecl);
670670

671671
// Cache for already-specialized function templates and any thunks they may

lib/ClangImporter/SwiftLookupTable.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,12 @@ void importer::addEntryToLookupTable(SwiftLookupTable &table,
19911991
}
19921992
}
19931993
}
1994+
if (auto usingDecl = dyn_cast<clang::UsingDecl>(named)) {
1995+
for (auto usingShadowDecl : usingDecl->shadows()) {
1996+
if (isa<clang::CXXMethodDecl>(usingShadowDecl->getTargetDecl()))
1997+
addEntryToLookupTable(table, usingShadowDecl, nameImporter);
1998+
}
1999+
}
19942000
}
19952001

19962002
/// Returns the nearest parent of \p module that is marked \c explicit in its

test/Interop/Cxx/class/inheritance/Inputs/module.modulemap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ module TypeAliases {
2828
header "type-aliases.h"
2929
}
3030

31+
module UsingBaseMembers {
32+
header "using-base-members.h"
33+
requires cplusplus
34+
}
35+
3136
module VirtualMethods {
3237
header "virtual-methods.h"
3338
requires cplusplus
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
struct PublicBase {
2+
private:
3+
int value;
4+
5+
public:
6+
int publicGetter() const { return 123; }
7+
void notExposed() const {}
8+
};
9+
10+
struct PublicBasePrivateInheritance : private PublicBase {
11+
using PublicBase::publicGetter;
12+
};
13+
14+
struct PublicBaseProtectedInheritance : protected PublicBase {
15+
using PublicBase::publicGetter;
16+
};
17+
18+
// TODO: make this work for protected base methods as well
19+
//struct ProtectedBase {
20+
//protected:
21+
// int protectedGetter() const { return 456; }
22+
//};
23+
//
24+
//struct ProtectedMemberPrivateInheritance : private ProtectedBase {
25+
// using ProtectedBase::protectedGetter;
26+
//};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -enable-experimental-cxx-interop
2+
3+
import UsingBaseMembers
4+
5+
let a = PublicBasePrivateInheritance()
6+
let _ = a.publicGetter()
7+
a.notExposed() // expected-error {{value of type 'PublicBasePrivateInheritance' has no member 'notExposed'}}
8+
9+
let b = PublicBaseProtectedInheritance()
10+
let _ = b.publicGetter()
11+
b.notExposed() // expected-error {{value of type 'PublicBaseProtectedInheritance' has no member 'notExposed'}}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -enable-experimental-cxx-interop)
2+
//
3+
// REQUIRES: executable_test
4+
5+
import StdlibUnittest
6+
import UsingBaseMembers
7+
8+
var UsingBaseTestSuite = TestSuite("Using Base Members")
9+
10+
UsingBaseTestSuite.test("PublicBasePrivateInheritance") {
11+
var p = PublicBasePrivateInheritance()
12+
expectEqual(123, p.publicGetter())
13+
}
14+
15+
UsingBaseTestSuite.test("PublicBaseProtectedInheritance") {
16+
var p = PublicBaseProtectedInheritance()
17+
expectEqual(123, p.publicGetter())
18+
}
19+
20+
runAllTests()

0 commit comments

Comments
 (0)