Skip to content

Commit c875b03

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 c875b03

13 files changed

+246
-79
lines changed

lib/ClangImporter/ImportDecl.cpp

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

3240+
bool postProcessFuncDecl(FuncDecl *func, ImportedName importedName,
3241+
bool importFuncWithoutSignature) {
3242+
auto dc = func->getDeclContext();
3243+
3244+
if (importedName.isSubscriptAccessor() && !importFuncWithoutSignature) {
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() && !importFuncWithoutSignature) {
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,13 @@ 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)
3568-
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;
3608-
}
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);
3614+
bool success =
3615+
postProcessFuncDecl(func, importedName, importFuncWithoutSignature);
3616+
if (!success)
3617+
return nullptr;
36153618
}
36163619

36173620
result->setIsObjC(false);
@@ -3924,13 +3927,16 @@ namespace {
39243927
}
39253928

39263929
Decl *VisitUsingDecl(const clang::UsingDecl *decl) {
3927-
// Using declarations are not imported.
3930+
// See VisitUsingShadowDecl below.
39283931
return nullptr;
39293932
}
39303933

39313934
Decl *VisitUsingShadowDecl(const clang::UsingShadowDecl *decl) {
3932-
// Only import types for now.
3933-
if (!isa<clang::TypeDecl>(decl->getUnderlyingDecl()))
3935+
// Only import:
3936+
// 1. Types
3937+
// 2. C++ methods from privately inherited base classes
3938+
if (!isa<clang::TypeDecl>(decl->getTargetDecl()) &&
3939+
!isa<clang::CXXMethodDecl>(decl->getTargetDecl()))
39343940
return nullptr;
39353941

39363942
ImportedName importedName;
@@ -3946,30 +3952,69 @@ namespace {
39463952
return importCompatibilityTypeAlias(decl, importedName,
39473953
*correctSwiftName);
39483954

3949-
auto DC =
3955+
auto importedDC =
39503956
Impl.importDeclContextOf(decl, importedName.getEffectiveContext());
3951-
if (!DC)
3957+
if (!importedDC)
39523958
return nullptr;
39533959

3954-
Decl *SwiftDecl = Impl.importDecl(decl->getUnderlyingDecl(), getActiveSwiftVersion());
3955-
if (!SwiftDecl)
3956-
return nullptr;
3960+
if (isa<clang::TypeDecl>(decl->getTargetDecl())) {
3961+
Decl *SwiftDecl = Impl.importDecl(decl->getUnderlyingDecl(), getActiveSwiftVersion());
3962+
if (!SwiftDecl)
3963+
return nullptr;
39573964

3958-
const TypeDecl *SwiftTypeDecl = dyn_cast<TypeDecl>(SwiftDecl);
3959-
if (!SwiftTypeDecl)
3960-
return nullptr;
3965+
const TypeDecl *SwiftTypeDecl = dyn_cast<TypeDecl>(SwiftDecl);
3966+
if (!SwiftTypeDecl)
3967+
return nullptr;
39613968

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());
3969+
auto Loc = Impl.importSourceLoc(decl->getLocation());
3970+
auto Result = Impl.createDeclWithClangNode<TypeAliasDecl>(
3971+
decl,
3972+
AccessLevel::Public,
3973+
Impl.importSourceLoc(decl->getBeginLoc()),
3974+
SourceLoc(), Name,
3975+
Loc,
3976+
/*genericparams*/nullptr, importedDC);
3977+
Result->setUnderlyingType(SwiftTypeDecl->getDeclaredInterfaceType());
3978+
3979+
return Result;
3980+
}
3981+
if (auto targetMethod =
3982+
dyn_cast<clang::CXXMethodDecl>(decl->getTargetDecl())) {
3983+
if (isa<clang::CXXConstructorDecl>(targetMethod))
3984+
return nullptr;
39713985

3972-
return Result;
3986+
auto dc = dyn_cast<clang::CXXRecordDecl>(decl->getDeclContext());
3987+
3988+
auto targetDC = targetMethod->getDeclContext();
3989+
auto targetRecord = dyn_cast<clang::CXXRecordDecl>(targetDC);
3990+
if (!targetRecord)
3991+
return nullptr;
3992+
3993+
// If this struct is not inherited from the struct where the method is
3994+
// defined, bail.
3995+
if (!dc->isDerivedFrom(targetRecord))
3996+
return nullptr;
3997+
3998+
auto importedBaseMethod = dyn_cast_or_null<FuncDecl>(
3999+
Impl.importDecl(targetMethod, getActiveSwiftVersion()));
4000+
// This will be nullptr for a protected method of base class that is
4001+
// made public with a using declaration in a derived class. This is
4002+
// valid in C++ but we do not import such using declarations now.
4003+
// TODO: make this work for protected base methods.
4004+
if (!importedBaseMethod)
4005+
return nullptr;
4006+
auto clonedMethod = dyn_cast_or_null<FuncDecl>(
4007+
Impl.importBaseMemberDecl(importedBaseMethod, importedDC));
4008+
if (!clonedMethod)
4009+
return nullptr;
4010+
4011+
bool success = postProcessFuncDecl(clonedMethod, importedName, false);
4012+
if (!success)
4013+
return nullptr;
4014+
4015+
return clonedMethod;
4016+
}
4017+
return nullptr;
39734018
}
39744019

39754020
/// Add an @objc(name) attribute with the given, optional name expressed as
@@ -8353,11 +8398,17 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
83538398
if (Result &&
83548399
(!Result->getDeclContext()->isModuleScopeContext() ||
83558400
isa<ClangModuleUnit>(Result->getDeclContext()))) {
8401+
// For using declarations that expose a method of a base class, the Clang
8402+
// decl is synthesized lazily when the method is actually used from Swift.
8403+
bool hasSynthesizedClangNode =
8404+
isa<clang::UsingShadowDecl>(ClangDecl) && isa<FuncDecl>(Result);
8405+
83568406
// Either the Swift declaration was from stdlib,
83578407
// or we imported the underlying decl of the typedef,
83588408
// or we imported the decl itself.
83598409
bool ImportedCorrectly =
83608410
!Result->getClangDecl() || SkippedOverTypedef ||
8411+
hasSynthesizedClangNode ||
83618412
Result->getClangDecl()->getCanonicalDecl() == Canon;
83628413

83638414
// Or the other type is a typedef,
@@ -8380,7 +8431,7 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
83808431
}
83818432
assert(ImportedCorrectly);
83828433
}
8383-
assert(Result->hasClangNode());
8434+
assert(Result->hasClangNode() || hasSynthesizedClangNode);
83848435
}
83858436
#else
83868437
(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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
struct PublicBase {
2+
private:
3+
int value;
4+
5+
public:
6+
int publicGetter() const { return 123; }
7+
void publicSetter(int v) { value = v; }
8+
void notExposed() const {}
9+
};
10+
11+
struct PublicBasePrivateInheritance : private PublicBase {
12+
using PublicBase::publicGetter;
13+
using PublicBase::publicSetter;
14+
};
15+
16+
// TODO: make this work for protected base methods as well
17+
//struct ProtectedBase {
18+
//protected:
19+
// int protectedGetter() const { return 456; }
20+
//};
21+
//
22+
//struct ProtectedMemberPrivateInheritance : private ProtectedBase {
23+
// using ProtectedBase::protectedGetter;
24+
//};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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'}}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
let p = PublicBasePrivateInheritance()
12+
expectEqual(123, p.publicGetter())
13+
p.publicSetter(456)
14+
expectEqual(456, p.publicGetter())
15+
}
16+
17+
runAllTests()

test/Interop/Cxx/operators/Inputs/member-inline.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,13 @@ struct DerivedFromConstIterator : public ConstIterator {};
418418

419419
struct DerivedFromConstIteratorPrivately : private ConstIterator {};
420420

421+
struct DerivedFromConstIteratorPrivatelyWithUsingDecl : private ConstIterator {
422+
using ConstIterator::operator*;
423+
};
424+
425+
struct DerivedFromAmbiguousOperatorStarPrivatelyWithUsingDecl
426+
: private AmbiguousOperatorStar {
427+
using AmbiguousOperatorStar::operator*;
428+
};
429+
421430
#endif

0 commit comments

Comments
 (0)