Skip to content

Commit 5eea81e

Browse files
committed
[cxx-interop] Implicitly defined copy constructors
1 parent ca0491a commit 5eea81e

File tree

8 files changed

+148
-48
lines changed

8 files changed

+148
-48
lines changed

include/swift/ClangImporter/ClangImporter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,12 @@ ValueDecl *getImportedMemberOperator(const DeclBaseName &name,
733733
/// as permissive as the input C++ access.
734734
AccessLevel convertClangAccess(clang::AccessSpecifier access);
735735

736+
/// Lookup and return the copy constructor of \a decl
737+
///
738+
/// Returns nullptr if \a decl doesn't have a valid copy constructor
739+
const clang::CXXConstructorDecl *
740+
findCopyConstructor(const clang::CXXRecordDecl *decl);
741+
736742
/// Read file IDs from 'private_fileid' Swift attributes on a Clang decl.
737743
///
738744
/// May return >1 fileID when a decl is annotated more than once, which should

lib/ClangImporter/ClangImporter.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8290,16 +8290,39 @@ bool importer::isViewType(const clang::CXXRecordDecl *decl) {
82908290
return !hasOwnedValueAttr(decl) && hasPointerInSubobjects(decl);
82918291
}
82928292

8293-
static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl) {
8294-
if (decl->hasSimpleCopyConstructor())
8295-
return true;
8293+
const clang::CXXConstructorDecl *
8294+
importer::findCopyConstructor(const clang::CXXRecordDecl *decl) {
8295+
for (auto ctor : decl->ctors()) {
8296+
if (ctor->isCopyConstructor() &&
8297+
// FIXME: Support default arguments (rdar://142414553)
8298+
ctor->getNumParams() == 1 && ctor->getAccess() == clang::AS_public &&
8299+
!ctor->isDeleted() && !ctor->isIneligibleOrNotSelected())
8300+
return ctor;
8301+
}
8302+
return nullptr;
8303+
}
82968304

8297-
return llvm::any_of(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
8298-
return ctor->isCopyConstructor() && !ctor->isDeleted() &&
8299-
!ctor->isIneligibleOrNotSelected() &&
8300-
// FIXME: Support default arguments (rdar://142414553)
8301-
ctor->getNumParams() == 1 &&
8302-
ctor->getAccess() == clang::AccessSpecifier::AS_public;
8305+
static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl,
8306+
ClangImporter::Implementation *importerImpl) {
8307+
if (!decl->hasSimpleCopyConstructor()) {
8308+
auto *copyCtor = findCopyConstructor(decl);
8309+
if (!copyCtor)
8310+
return false;
8311+
8312+
if (!copyCtor->isDefaulted())
8313+
return true;
8314+
}
8315+
8316+
// If the copy constructor is defaulted or implicitly declared, we should only
8317+
// import the type as copyable if all its fields are also copyable
8318+
// FIXME: we should also look at the bases
8319+
return llvm::none_of(decl->fields(), [&](clang::FieldDecl *field) {
8320+
if (const auto *rd = field->getType()->getAsRecordDecl()) {
8321+
return (!field->getType()->isReferenceType() &&
8322+
!field->getType()->isPointerType() &&
8323+
recordHasMoveOnlySemantics(rd, importerImpl));
8324+
}
8325+
return false;
83038326
});
83048327
}
83058328

@@ -8498,8 +8521,8 @@ CxxValueSemantics::evaluate(Evaluator &evaluator,
84988521
return CxxValueSemanticsKind::Copyable;
84998522
}
85008523

8501-
bool isCopyable = !hasNonCopyableAttr(cxxRecordDecl) &&
8502-
hasCopyTypeOperations(cxxRecordDecl);
8524+
bool isCopyable = !hasNonCopyableAttr(cxxRecordDecl) &&
8525+
hasCopyTypeOperations(cxxRecordDecl, importerImpl);
85038526
bool isMovable = hasMoveTypeOperations(cxxRecordDecl);
85048527

85058528
if (!hasDestroyTypeOperations(cxxRecordDecl) || (!isCopyable && !isMovable)) {

lib/ClangImporter/ImportDecl.cpp

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ bool importer::recordHasReferenceSemantics(
187187
return semanticsKind == CxxRecordSemanticsKind::Reference;
188188
}
189189

190+
bool importer::recordHasMoveOnlySemantics(
191+
const clang::RecordDecl *decl,
192+
ClangImporter::Implementation *importerImpl) {
193+
auto semanticsKind = evaluateOrDefault(
194+
importerImpl->SwiftContext.evaluator,
195+
CxxValueSemantics({decl->getTypeForDecl(), importerImpl}), {});
196+
return semanticsKind == CxxValueSemanticsKind::MoveOnly;
197+
}
198+
190199
bool importer::hasImmortalAttrs(const clang::RecordDecl *decl) {
191200
return decl->hasAttrs() && llvm::any_of(decl->getAttrs(), [](auto *attr) {
192201
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
@@ -2096,10 +2105,7 @@ namespace {
20962105
}
20972106

20982107
bool recordHasMoveOnlySemantics(const clang::RecordDecl *decl) {
2099-
auto semanticsKind = evaluateOrDefault(
2100-
Impl.SwiftContext.evaluator,
2101-
CxxValueSemantics({decl->getTypeForDecl(), &Impl}), {});
2102-
return semanticsKind == CxxValueSemanticsKind::MoveOnly;
2108+
return importer::recordHasMoveOnlySemantics(decl, &Impl);
21032109
}
21042110

21052111
void markReturnsUnsafeNonescapable(AbstractFunctionDecl *fd) {
@@ -2278,15 +2284,6 @@ namespace {
22782284
loc, ArrayRef<InheritedEntry>(), nullptr, dc);
22792285
Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = result;
22802286

2281-
if (recordHasMoveOnlySemantics(decl)) {
2282-
if (decl->isInStdNamespace() && decl->getName() == "promise") {
2283-
// Do not import std::promise.
2284-
return nullptr;
2285-
}
2286-
result->addAttribute(new (Impl.SwiftContext)
2287-
MoveOnlyAttr(/*Implicit=*/true));
2288-
}
2289-
22902287
// FIXME: Figure out what to do with superclasses in C++. One possible
22912288
// solution would be to turn them into members and add conversion
22922289
// functions.
@@ -2503,6 +2500,15 @@ namespace {
25032500
cxxRecordDecl->ctors().empty());
25042501
}
25052502

2503+
if (recordHasMoveOnlySemantics(decl)) {
2504+
if (decl->isInStdNamespace() && decl->getName() == "promise") {
2505+
// Do not import std::promise.
2506+
return nullptr;
2507+
}
2508+
result->getAttrs().add(new (Impl.SwiftContext)
2509+
MoveOnlyAttr(/*Implicit=*/true));
2510+
}
2511+
25062512
// TODO: builtin "zeroInitializer" does not work with non-escapable
25072513
// types yet. Don't generate an initializer.
25082514
if (hasZeroInitializableStorage && needsEmptyInitializer &&
@@ -3098,11 +3104,10 @@ namespace {
30983104
// instantiate its copy constructor.
30993105
bool isExplicitlyNonCopyable = hasNonCopyableAttr(decl);
31003106

3101-
clang::CXXConstructorDecl *copyCtor = nullptr;
31023107
clang::CXXConstructorDecl *moveCtor = nullptr;
31033108
clang::CXXConstructorDecl *defaultCtor = nullptr;
31043109
if (decl->needsImplicitCopyConstructor() && !isExplicitlyNonCopyable) {
3105-
copyCtor = clangSema.DeclareImplicitCopyConstructor(
3110+
clangSema.DeclareImplicitCopyConstructor(
31063111
const_cast<clang::CXXRecordDecl *>(decl));
31073112
}
31083113
if (decl->needsImplicitMoveConstructor()) {
@@ -3123,10 +3128,7 @@ namespace {
31233128
// Note: we use "doesThisDeclarationHaveABody" here because
31243129
// that's what "DefineImplicitCopyConstructor" checks.
31253130
!declCtor->doesThisDeclarationHaveABody()) {
3126-
if (declCtor->isCopyConstructor()) {
3127-
if (!copyCtor && !isExplicitlyNonCopyable)
3128-
copyCtor = declCtor;
3129-
} else if (declCtor->isMoveConstructor()) {
3131+
if (declCtor->isMoveConstructor()) {
31303132
if (!moveCtor)
31313133
moveCtor = declCtor;
31323134
} else if (declCtor->isDefaultConstructor()) {
@@ -3136,11 +3138,6 @@ namespace {
31363138
}
31373139
}
31383140
}
3139-
if (copyCtor && !isExplicitlyNonCopyable &&
3140-
!decl->isAnonymousStructOrUnion()) {
3141-
clangSema.DefineImplicitCopyConstructor(clang::SourceLocation(),
3142-
copyCtor);
3143-
}
31443141
if (moveCtor && !decl->isAnonymousStructOrUnion()) {
31453142
clangSema.DefineImplicitMoveConstructor(clang::SourceLocation(),
31463143
moveCtor);

lib/ClangImporter/ImporterImpl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,11 @@ namespace importer {
19741974
bool recordHasReferenceSemantics(const clang::RecordDecl *decl,
19751975
ClangImporter::Implementation *importerImpl);
19761976

1977+
/// Returns true if the given C/C++ record should be imported as non-copyable into
1978+
/// Swift.
1979+
bool recordHasMoveOnlySemantics(const clang::RecordDecl *decl,
1980+
ClangImporter::Implementation *importerImpl);
1981+
19771982
/// Whether this is a forward declaration of a type. We ignore forward
19781983
/// declarations in certain cases, and instead process the real declarations.
19791984
bool isForwardDeclOfType(const clang::Decl *decl);

lib/IRGen/GenStruct.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -553,15 +553,7 @@ namespace {
553553
const auto *cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(ClangDecl);
554554
if (!cxxRecordDecl)
555555
return nullptr;
556-
for (auto ctor : cxxRecordDecl->ctors()) {
557-
if (ctor->isCopyConstructor() &&
558-
// FIXME: Support default arguments (rdar://142414553)
559-
ctor->getNumParams() == 1 &&
560-
ctor->getAccess() == clang::AS_public && !ctor->isDeleted() &&
561-
!ctor->isIneligibleOrNotSelected())
562-
return ctor;
563-
}
564-
return nullptr;
556+
return importer::findCopyConstructor(cxxRecordDecl);
565557
}
566558

567559
const clang::CXXConstructorDecl *findMoveConstructor() const {
@@ -629,7 +621,18 @@ namespace {
629621

630622
auto &ctx = IGF.IGM.Context;
631623
auto *importer = static_cast<ClangImporter *>(ctx.getClangModuleLoader());
632-
624+
625+
if (copyConstructor->isDefaulted() &&
626+
copyConstructor->getAccess() == clang::AS_public &&
627+
!copyConstructor->isDeleted() &&
628+
// Note: we use "doesThisDeclarationHaveABody" here because
629+
// that's what "DefineImplicitCopyConstructor" checks.
630+
!copyConstructor->doesThisDeclarationHaveABody()) {
631+
importer->getClangSema().DefineImplicitCopyConstructor(
632+
clang::SourceLocation(),
633+
const_cast<clang::CXXConstructorDecl *>(copyConstructor));
634+
}
635+
633636
auto &diagEngine = importer->getClangSema().getDiagnostics();
634637
clang::DiagnosticErrorTrap trap(diagEngine);
635638
auto clangFnAddr =

test/Interop/Cxx/class/noncopyable-typechecker.swift

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
3-
// RUN: %target-swift-frontend -cxx-interoperability-mode=default -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t%{fs-sep}Inputs %t%{fs-sep}test.swift -verify-additional-file %t%{fs-sep}Inputs%{fs-sep}noncopyable.h
4-
// RUN: %target-swift-frontend -cxx-interoperability-mode=default -Xcc -std=c++20 -verify-additional-prefix cpp20- -D CPP20 -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t%{fs-sep}Inputs %t%{fs-sep}test.swift -verify-additional-file %t%{fs-sep}Inputs%{fs-sep}noncopyable.h
3+
// RUN: %target-swift-frontend -cxx-interoperability-mode=default -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t%{fs-sep}Inputs %t%{fs-sep}test.swift -verify-additional-file %t%{fs-sep}Inputs%{fs-sep}noncopyable.h -verify-ignore-unrelated
4+
// RUN: %target-swift-frontend -cxx-interoperability-mode=default -Xcc -std=c++20 -verify-additional-prefix cpp20- -D CPP20 -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t%{fs-sep}Inputs %t%{fs-sep}test.swift -verify-additional-file %t%{fs-sep}Inputs%{fs-sep}noncopyable.h -verify-ignore-unrelated
55

66
//--- Inputs/module.modulemap
77
module Test {
@@ -15,7 +15,7 @@ module Test {
1515

1616
struct NonCopyable {
1717
NonCopyable() = default;
18-
NonCopyable(const NonCopyable& other) = delete;
18+
NonCopyable(const NonCopyable& other) = delete; // expected-note {{'NonCopyable' has been explicitly marked deleted here}}
1919
NonCopyable(NonCopyable&& other) = default;
2020
};
2121

@@ -79,6 +79,44 @@ struct SWIFT_NONCOPYABLE NonCopyableNonMovable { // expected-note {{record 'NonC
7979
NonCopyableNonMovable(NonCopyableNonMovable&& other) = delete;
8080
};
8181

82+
struct ImplicitCopyConstructor {
83+
NonCopyable element;
84+
};
85+
86+
template <typename T, typename P, typename R>
87+
struct TemplatedImplicitCopyConstructor {
88+
T element;
89+
P *pointer;
90+
R &reference;
91+
};
92+
93+
using NonCopyableT = TemplatedImplicitCopyConstructor<NonCopyable, int, int>;
94+
using NonCopyableP = TemplatedImplicitCopyConstructor<int, NonCopyable, int>;
95+
using NonCopyableR = TemplatedImplicitCopyConstructor<int, int, NonCopyable>;
96+
97+
struct DefaultedCopyConstructor {
98+
NonCopyable element; // expected-note {{copy constructor of 'DefaultedCopyConstructor' is implicitly deleted because field 'element' has a deleted copy constructor}}
99+
DefaultedCopyConstructor(const DefaultedCopyConstructor&) = default;
100+
// expected-warning@-1 {{explicitly defaulted copy constructor is implicitly deleted}}
101+
// expected-note@-2 {{replace 'default' with 'delete'}}
102+
DefaultedCopyConstructor(DefaultedCopyConstructor&&) = default;
103+
};
104+
105+
template<typename T>
106+
struct TemplatedDefaultedCopyConstructor {
107+
T element;
108+
TemplatedDefaultedCopyConstructor(const TemplatedDefaultedCopyConstructor&) = default;
109+
TemplatedDefaultedCopyConstructor(TemplatedDefaultedCopyConstructor&&) = default;
110+
};
111+
112+
template<typename T>
113+
struct DerivedTemplatedDefaultedCopyConstructor : TemplatedDefaultedCopyConstructor<T> {};
114+
115+
using CopyableDefaultedCopyConstructor = TemplatedDefaultedCopyConstructor<NonCopyableP>;
116+
using NonCopyableDefaultedCopyConstructor = TemplatedDefaultedCopyConstructor<NonCopyable>;
117+
using CopyableDerived = DerivedTemplatedDefaultedCopyConstructor<NonCopyableR>;
118+
using NonCopyableDerived = DerivedTemplatedDefaultedCopyConstructor<NonCopyable>;
119+
82120
template<typename T> struct SWIFT_COPYABLE_IF(T) DisposableContainer {};
83121
struct POD { int x; float y; }; // special members are implicit, but should be copyable
84122
using DisposablePOD = DisposableContainer<POD>; // should also be copyable
@@ -133,6 +171,23 @@ func missingLifetimeOperation() {
133171
takeCopyable(s)
134172
}
135173

174+
func implicitCopyConstructor(i: borrowing ImplicitCopyConstructor, t: borrowing NonCopyableT, p: borrowing NonCopyableP, r: borrowing NonCopyableR) {
175+
takeCopyable(i) // expected-error {{global function 'takeCopyable' requires that 'ImplicitCopyConstructor' conform to 'Copyable'}}
176+
takeCopyable(t) // expected-error {{global function 'takeCopyable' requires that 'NonCopyableT' (aka 'TemplatedImplicitCopyConstructor<NonCopyable, CInt, CInt>') conform to 'Copyable'}}
177+
178+
// References and pointers to non-copyable types are still copyable
179+
takeCopyable(p)
180+
takeCopyable(r)
181+
}
182+
183+
func defaultCopyConstructor(d: borrowing DefaultedCopyConstructor, d1: borrowing CopyableDefaultedCopyConstructor, d2: borrowing NonCopyableDefaultedCopyConstructor, d3: borrowing CopyableDerived, d4: borrowing NonCopyableDerived) {
184+
takeCopyable(d) // expected-error {{global function 'takeCopyable' requires that 'DefaultedCopyConstructor' conform to 'Copyable'}}
185+
takeCopyable(d1)
186+
takeCopyable(d2) // expected-error {{global function 'takeCopyable' requires that 'NonCopyableDefaultedCopyConstructor' (aka 'TemplatedDefaultedCopyConstructor<NonCopyable>') conform to 'Copyable'}}
187+
takeCopyable(d3)
188+
takeCopyable(d4) // expected-error {{global function 'takeCopyable' requires that 'NonCopyableDerived' (aka 'DerivedTemplatedDefaultedCopyConstructor<NonCopyable>') conform to 'Copyable'}}
189+
}
190+
136191
func copyableDisposablePOD(p: DisposablePOD) {
137192
takeCopyable(p)
138193
}

test/Interop/Cxx/stdlib/Inputs/std-unique-ptr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,9 @@ struct CountCopies {
8989

9090
inline std::unique_ptr<CountCopies> getCopyCountedUniquePtr() { return std::make_unique<CountCopies>(); }
9191

92+
struct HasUniqueIntVector {
93+
HasUniqueIntVector() = default;
94+
std::vector<std::unique_ptr<int>> x;
95+
};
96+
9297
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_UNIQUE_PTR_H

test/Interop/Cxx/stdlib/use-std-unique-ptr-typechecker.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ func takeCopyable<T: Copyable>(_ x: T) {}
1010
let vecUniquePtr = getVectorNonCopyableUniquePtr()
1111
takeCopyable(vecUniquePtr)
1212
// CHECK: error: global function 'takeCopyable' requires that 'std{{.*}}vector{{.*}}unique_ptr{{.*}}NonCopyable{{.*}}' conform to 'Copyable'
13+
14+
let uniqueIntVec = HasUniqueIntVector()
15+
takeCopyable(uniqueIntVec)
16+
// CHECK: error: global function 'takeCopyable' requires that 'HasUniqueIntVector' conform to 'Copyable'
17+
18+

0 commit comments

Comments
 (0)