Skip to content

Commit e2f6133

Browse files
authored
Merge pull request #76892 from swiftlang/egorzhdan/cxx-private-fields
[cxx-interop] Import private fields of C++ structs
2 parents 59d3af1 + 6943986 commit e2f6133

13 files changed

+128
-20
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5777,6 +5777,9 @@ makeBaseClassMemberAccessors(DeclContext *declContext,
57775777
bodyParams = ParameterList::createEmpty(ctx);
57785778
}
57795779

5780+
assert(baseClassVar->getFormalAccess() == AccessLevel::Public &&
5781+
"base class member must be public");
5782+
57805783
auto getterDecl = AccessorDecl::create(
57815784
ctx,
57825785
/*FuncLoc=*/SourceLoc(),
@@ -7325,6 +7328,10 @@ Decl *ClangImporter::importDeclDirectly(const clang::NamedDecl *decl) {
73257328

73267329
ValueDecl *ClangImporter::Implementation::importBaseMemberDecl(
73277330
ValueDecl *decl, DeclContext *newContext) {
7331+
// Do not clone private C++ decls.
7332+
if (decl->getFormalAccess() < AccessLevel::Public)
7333+
return nullptr;
7334+
73287335
// Make sure we don't clone the decl again for this class, as that would
73297336
// result in multiple definitions of the same symbol.
73307337
std::pair<ValueDecl *, DeclContext *> key = {decl, newContext};

lib/ClangImporter/ImportDecl.cpp

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4117,8 +4117,17 @@ namespace {
41174117

41184118
auto type = importedType.getType();
41194119

4120+
// Private C++ fields should also be private in Swift. Since Swift does
4121+
// not have a notion of protected field, map protected C++ fields to
4122+
// private Swift fields.
4123+
AccessLevel accessLevel =
4124+
(decl->getAccess() == clang::AccessSpecifier::AS_private ||
4125+
decl->getAccess() == clang::AccessSpecifier::AS_protected)
4126+
? AccessLevel::Private
4127+
: AccessLevel::Public;
4128+
41204129
auto result =
4121-
Impl.createDeclWithClangNode<VarDecl>(decl, AccessLevel::Public,
4130+
Impl.createDeclWithClangNode<VarDecl>(decl, accessLevel,
41224131
/*IsStatic*/ false,
41234132
VarDecl::Introducer::Var,
41244133
Impl.importSourceLoc(decl->getLocation()),
@@ -8825,15 +8834,15 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
88258834
if (ClangDecl->isInvalidDecl())
88268835
return nullptr;
88278836

8828-
// Private and protected C++ class members should never be used, so we skip
8829-
// them entirely (instead of importing them with a corresponding Swift access
8830-
// level) to remove clutter from the module interface.
8831-
//
8832-
// We omit protected members in addition to private members because Swift
8833-
// structs can't inherit from C++ classes, so there's effectively no way to
8834-
// access them.
8837+
// Private and protected C++ class members should never be used from Swift,
8838+
// however, parts of the Swift typechecker rely on being able to iterate over
8839+
// all of the stored fields of a particular struct. This means we still need
8840+
// to add private fields to the Swift AST.
8841+
//
8842+
// Other kinds of private and protected C++ decls are not relevant for Swift.
88358843
clang::AccessSpecifier access = ClangDecl->getAccess();
8836-
if (access == clang::AS_protected || access == clang::AS_private)
8844+
if ((access == clang::AS_protected || access == clang::AS_private) &&
8845+
!isa<clang::FieldDecl>(ClangDecl))
88378846
return nullptr;
88388847

88398848
bool SkippedOverTypedef = false;
@@ -9530,15 +9539,18 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl(
95309539
for (auto member: members) {
95319540
// This means we found a member in a C++ record's base class.
95329541
if (swiftDecl->getClangDecl() != clangRecord) {
9542+
auto namedMember = cast<ValueDecl>(member);
9543+
if (namedMember->getFormalAccess() < AccessLevel::Public)
9544+
continue;
95339545
// Do not clone the base member into the derived class
95349546
// when the derived class already has a member of such
95359547
// name and arity.
95369548
auto memberArity =
9537-
getImportedBaseMemberDeclArity(cast<ValueDecl>(member));
9549+
getImportedBaseMemberDeclArity(namedMember);
95389550
bool shouldAddBaseMember = true;
95399551
for (const auto *currentMember : swiftDecl->getMembers()) {
95409552
auto vd = dyn_cast<ValueDecl>(currentMember);
9541-
if (vd->getName() == cast<ValueDecl>(member)->getName()) {
9553+
if (vd->getName() == namedMember->getName()) {
95429554
if (memberArity == getImportedBaseMemberDeclArity(vd)) {
95439555
shouldAddBaseMember = false;
95449556
break;
@@ -9548,7 +9560,7 @@ void ClangImporter::Implementation::loadAllMembersOfRecordDecl(
95489560
if (!shouldAddBaseMember)
95499561
continue;
95509562
// So we need to clone the member into the derived class.
9551-
if (auto newDecl = importBaseMemberDecl(cast<ValueDecl>(member), swiftDecl)) {
9563+
if (auto newDecl = importBaseMemberDecl(namedMember, swiftDecl)) {
95529564
swiftDecl->addMember(newDecl);
95539565
}
95549566
continue;

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static AccessorDecl *makeFieldGetterDecl(ClangImporter::Implementation &Impl,
9696
/*Throws=*/false,
9797
/*ThrowsLoc=*/SourceLoc(), /*ThrownType=*/TypeLoc(),
9898
params, getterType, importedDecl, clangNode);
99-
getterDecl->setAccess(AccessLevel::Public);
99+
getterDecl->setAccess(importedFieldDecl->getFormalAccess());
100100
getterDecl->setIsObjC(false);
101101
getterDecl->setIsDynamic(false);
102102

@@ -128,7 +128,7 @@ static AccessorDecl *makeFieldSetterDecl(ClangImporter::Implementation &Impl,
128128
setterDecl->setIsObjC(false);
129129
setterDecl->setIsDynamic(false);
130130
setterDecl->setSelfAccessKind(SelfAccessKind::Mutating);
131-
setterDecl->setAccess(AccessLevel::Public);
131+
setterDecl->setAccess(importedFieldDecl->getFormalAccess());
132132

133133
return setterDecl;
134134
}

lib/Sema/TypeCheckInvertible.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "TypeCheckInvertible.h"
2020
#include "swift/AST/ASTContext.h"
21+
#include "swift/AST/ClangModuleLoader.h"
2122
#include "swift/AST/GenericEnvironment.h"
2223
#include "swift/Basic/Assertions.h"
2324
#include "TypeChecker.h"
@@ -309,6 +310,23 @@ bool StorageVisitor::visit(NominalTypeDecl *nominal, DeclContext *dc) {
309310
return true;
310311
}
311312

313+
// If this is a C++ struct, walk the members of its base types.
314+
if (auto cxxRecordDecl =
315+
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl())) {
316+
for (auto cxxBase : cxxRecordDecl->bases()) {
317+
if (auto cxxBaseDecl = cxxBase.getType()->getAsCXXRecordDecl()) {
318+
auto clangModuleLoader = dc->getASTContext().getClangModuleLoader();
319+
auto importedDecl =
320+
clangModuleLoader->importDeclDirectly(cxxBaseDecl);
321+
if (auto nominalBaseDecl =
322+
dyn_cast_or_null<NominalTypeDecl>(importedDecl)) {
323+
if (visit(nominalBaseDecl, dc))
324+
return true;
325+
}
326+
}
327+
}
328+
}
329+
312330
return false;
313331
}
314332

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ module ProtocolConformance {
7878
requires cplusplus
7979
}
8080

81+
module Sendable {
82+
header "sendable.h"
83+
requires cplusplus
84+
}
85+
8186
module StructuredBindingsGetMethod {
8287
header "structured-bindings-get-method.h"
8388
requires cplusplus
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
struct HasPrivatePointerField {
2+
private:
3+
const int *ptr;
4+
};
5+
6+
struct HasProtectedPointerField {
7+
protected:
8+
const int *ptr;
9+
};
10+
11+
struct HasPublicPointerField {
12+
const int *ptr;
13+
};
14+
15+
struct HasPrivateNonSendableField {
16+
private:
17+
HasPrivatePointerField f;
18+
};
19+
20+
struct HasProtectedNonSendableField {
21+
protected:
22+
HasProtectedPointerField f;
23+
};
24+
25+
struct HasPublicNonSendableField {
26+
HasPublicPointerField f;
27+
};
28+
29+
struct DerivedFromHasPublicPointerField : HasPublicPointerField {};
30+
struct DerivedFromHasPublicNonSendableField : HasPublicNonSendableField {};
31+
struct DerivedFromHasPrivatePointerField : HasPrivatePointerField {};
32+
33+
struct DerivedPrivatelyFromHasPublicPointerField : private HasPublicPointerField {};
34+
struct DerivedPrivatelyFromHasPublicNonSendableField : private HasPublicNonSendableField {};
35+
struct DerivedPrivatelyFromHasPrivatePointerField : private HasPrivatePointerField {};
36+
37+
struct DerivedProtectedFromHasPublicPointerField : protected HasPublicPointerField {};
38+
struct DerivedProtectedFromHasPublicNonSendableField : protected HasPublicNonSendableField {};
39+
struct DerivedProtectedFromHasPrivatePointerField : protected HasPrivatePointerField {};

test/Interop/Cxx/class/access-specifiers-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Test module interface produced for C++ access specifiers test.
22
// In particular, we don't want any of the private members showing up here.
33

4-
// RUN: %target-swift-ide-test -print-module -module-to-print=AccessSpecifiers -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
4+
// RUN: %target-swift-ide-test -print-module -module-to-print=AccessSpecifiers -access-filter-public -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
55

66
// CHECK: struct PublicPrivate {
77
// CHECK-NEXT: init()

test/Interop/Cxx/class/access-specifiers-typechecker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var publicFlagEnumVar: PublicPrivate.PublicFlagEnum
2626

2727
// Cannot access any private members and types.
2828

29-
v.PrivateMemberVar = 1 // expected-error {{value of type 'PublicPrivate' has no member 'PrivateMemberVar'}}
29+
v.PrivateMemberVar = 1 // expected-error {{'PrivateMemberVar' is inaccessible due to 'private' protection level}}
3030
PublicPrivate.PrivateStaticMemberVar = 1 // expected-error {{'PublicPrivate' has no member 'PrivateStaticMemberVar'}}
3131
v.privateMemberFunc() // expected-error {{value of type 'PublicPrivate' has no member 'privateMemberFunc'}}
3232

test/Interop/Cxx/class/inheritance/using-base-members-module-interface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-ide-test -print-module -module-to-print=UsingBaseMembers -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-6 | %FileCheck %s
2-
// RUN: %target-swift-ide-test -print-module -module-to-print=UsingBaseMembers -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift | %FileCheck %s
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=UsingBaseMembers -access-filter-public -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-6 | %FileCheck %s
2+
// RUN: %target-swift-ide-test -print-module -module-to-print=UsingBaseMembers -access-filter-public -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift | %FileCheck %s
33

44
// CHECK: struct PublicBase {
55
// CHECK-NEXT: init()

test/Interop/Cxx/class/memberwise-initializer-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberwiseInitializer -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberwiseInitializer -access-filter-public -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
22

33
// CHECK: struct StructPrivateOnly {
44
// CHECK-NEXT: init()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -swift-version 6 -cxx-interoperability-mode=upcoming-swift
2+
3+
import Sendable // expected-warning {{add '@preconcurrency' to treat 'Sendable'-related errors from module 'Sendable' as warnings}}
4+
5+
func takesSendable<T: Sendable>(_ x: T.Type) {}
6+
7+
takesSendable(HasPrivatePointerField.self) // expected-error {{type 'HasPrivatePointerField' does not conform to the 'Sendable' protocol}}
8+
takesSendable(HasProtectedPointerField.self) // expected-error {{type 'HasProtectedPointerField' does not conform to the 'Sendable' protocol}}
9+
takesSendable(HasPublicPointerField.self) // expected-error {{type 'HasPublicPointerField' does not conform to the 'Sendable' protocol}}
10+
11+
takesSendable(HasPrivateNonSendableField.self) // expected-error {{type 'HasPrivateNonSendableField' does not conform to the 'Sendable' protocol}}
12+
takesSendable(HasProtectedNonSendableField.self) // expected-error {{type 'HasProtectedNonSendableField' does not conform to the 'Sendable' protocol}}
13+
takesSendable(HasPublicNonSendableField.self) // expected-error {{type 'HasPublicNonSendableField' does not conform to the 'Sendable' protocol}}
14+
15+
takesSendable(DerivedFromHasPublicPointerField.self) // expected-error {{type 'DerivedFromHasPublicPointerField' does not conform to the 'Sendable' protocol}}
16+
takesSendable(DerivedFromHasPublicNonSendableField.self) // expected-error {{type 'DerivedFromHasPublicNonSendableField' does not conform to the 'Sendable' protocol}}
17+
takesSendable(DerivedFromHasPrivatePointerField.self) // expected-error {{type 'DerivedFromHasPrivatePointerField' does not conform to the 'Sendable' protocol}}
18+
19+
takesSendable(DerivedPrivatelyFromHasPublicPointerField.self) // expected-error {{type 'DerivedPrivatelyFromHasPublicPointerField' does not conform to the 'Sendable' protocol}}
20+
takesSendable(DerivedPrivatelyFromHasPublicNonSendableField.self) // expected-error {{type 'DerivedPrivatelyFromHasPublicNonSendableField' does not conform to the 'Sendable' protocol}}
21+
takesSendable(DerivedPrivatelyFromHasPrivatePointerField.self) // expected-error {{type 'DerivedPrivatelyFromHasPrivatePointerField' does not conform to the 'Sendable' protocol}}
22+
23+
takesSendable(DerivedProtectedFromHasPublicPointerField.self) // expected-error {{type 'DerivedProtectedFromHasPublicPointerField' does not conform to the 'Sendable' protocol}}
24+
takesSendable(DerivedProtectedFromHasPublicNonSendableField.self) // expected-error {{type 'DerivedProtectedFromHasPublicNonSendableField' does not conform to the 'Sendable' protocol}}
25+
takesSendable(DerivedProtectedFromHasPrivatePointerField.self) // expected-error {{type 'DerivedProtectedFromHasPrivatePointerField' does not conform to the 'Sendable' protocol}}

test/Interop/Cxx/ergonomics/implicit-computed-properties-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@
263263

264264
// CHECK: struct PrivatePropertyWithSameName {
265265
// CHECK-NEXT: init()
266-
// CHECK-NEXT: var value: Int32
267266
// CHECK-NEXT: func getValue() -> Int32
268267
// CHECK-NEXT: mutating func setValue(_ i: Int32)
268+
// CHECK-NEXT: var value: Int32
269269
// CHECK-NEXT: }
270270

271271
// CHECK: struct SnakeCaseGetterSetter {

test/Interop/Cxx/operators/member-inline-module-interface.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@
243243
// CHECK-NEXT: mutating func __operatorStar() -> UnsafeMutablePointer<Int32>
244244
// CHECK-NEXT: @available(*, unavailable, message: "use .pointee property")
245245
// CHECK-NEXT: func __operatorStar() -> UnsafePointer<Int32>
246+
// CHECK-NEXT: var value: Int32
246247
// CHECK-NEXT: }
247248

248249
// CHECK: struct AmbiguousOperatorStar2 {
@@ -254,6 +255,7 @@
254255
// CHECK-NEXT: func __operatorStar() -> UnsafePointer<Int32>
255256
// CHECK-NEXT: @available(*, unavailable, message: "use .pointee property")
256257
// CHECK-NEXT: func __operatorStar() -> UnsafePointer<Int32>
258+
// CHECK-NEXT: var value: Int32
257259
// CHECK-NEXT: }
258260

259261
// CHECK: struct DerivedFromConstIterator {

0 commit comments

Comments
 (0)