Skip to content

Commit a6f4e41

Browse files
authored
Merge pull request #84646 from susmonteiro/susmonteiro/implicit-copy-constructors
[cxx-interop] Implicitly defined copy constructors
2 parents fd6edb8 + 1c5bbe0 commit a6f4e41

File tree

8 files changed

+147
-46
lines changed

8 files changed

+147
-46
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
@@ -8315,16 +8315,39 @@ bool importer::isViewType(const clang::CXXRecordDecl *decl) {
83158315
return !hasOwnedValueAttr(decl) && hasPointerInSubobjects(decl);
83168316
}
83178317

8318-
static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl) {
8319-
if (decl->hasSimpleCopyConstructor())
8320-
return true;
8318+
const clang::CXXConstructorDecl *
8319+
importer::findCopyConstructor(const clang::CXXRecordDecl *decl) {
8320+
for (auto ctor : decl->ctors()) {
8321+
if (ctor->isCopyConstructor() &&
8322+
// FIXME: Support default arguments (rdar://142414553)
8323+
ctor->getNumParams() == 1 && ctor->getAccess() == clang::AS_public &&
8324+
!ctor->isDeleted() && !ctor->isIneligibleOrNotSelected())
8325+
return ctor;
8326+
}
8327+
return nullptr;
8328+
}
83218329

8322-
return llvm::any_of(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
8323-
return ctor->isCopyConstructor() && !ctor->isDeleted() &&
8324-
!ctor->isIneligibleOrNotSelected() &&
8325-
// FIXME: Support default arguments (rdar://142414553)
8326-
ctor->getNumParams() == 1 &&
8327-
ctor->getAccess() == clang::AccessSpecifier::AS_public;
8330+
static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl,
8331+
ClangImporter::Implementation *importerImpl) {
8332+
if (!decl->hasSimpleCopyConstructor()) {
8333+
auto *copyCtor = findCopyConstructor(decl);
8334+
if (!copyCtor)
8335+
return false;
8336+
8337+
if (!copyCtor->isDefaulted())
8338+
return true;
8339+
}
8340+
8341+
// If the copy constructor is defaulted or implicitly declared, we should only
8342+
// import the type as copyable if all its fields are also copyable
8343+
// FIXME: we should also look at the bases
8344+
return llvm::none_of(decl->fields(), [&](clang::FieldDecl *field) {
8345+
if (const auto *rd = field->getType()->getAsRecordDecl()) {
8346+
return (!field->getType()->isReferenceType() &&
8347+
!field->getType()->isPointerType() &&
8348+
recordHasMoveOnlySemantics(rd, importerImpl));
8349+
}
8350+
return false;
83288351
});
83298352
}
83308353

@@ -8523,8 +8546,8 @@ CxxValueSemantics::evaluate(Evaluator &evaluator,
85238546
return CxxValueSemanticsKind::Copyable;
85248547
}
85258548

8526-
bool isCopyable = !hasNonCopyableAttr(cxxRecordDecl) &&
8527-
hasCopyTypeOperations(cxxRecordDecl);
8549+
bool isCopyable = !hasNonCopyableAttr(cxxRecordDecl) &&
8550+
hasCopyTypeOperations(cxxRecordDecl, importerImpl);
85288551
bool isMovable = hasMoveTypeOperations(cxxRecordDecl);
85298552

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

lib/ClangImporter/ImportDecl.cpp

Lines changed: 20 additions & 22 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,13 +2284,9 @@ 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));
2287+
if (decl->isInStdNamespace() && decl->getName() == "promise" && recordHasMoveOnlySemantics(decl)) {
2288+
// Do not import std::promise.
2289+
return nullptr;
22882290
}
22892291

22902292
// FIXME: Figure out what to do with superclasses in C++. One possible
@@ -2503,6 +2505,11 @@ namespace {
25032505
cxxRecordDecl->ctors().empty());
25042506
}
25052507

2508+
if (recordHasMoveOnlySemantics(decl)) {
2509+
result->getAttrs().add(new (Impl.SwiftContext)
2510+
MoveOnlyAttr(/*Implicit=*/true));
2511+
}
2512+
25062513
// TODO: builtin "zeroInitializer" does not work with non-escapable
25072514
// types yet. Don't generate an initializer.
25082515
if (hasZeroInitializableStorage && needsEmptyInitializer &&
@@ -3098,11 +3105,10 @@ namespace {
30983105
// instantiate its copy constructor.
30993106
bool isExplicitlyNonCopyable = hasNonCopyableAttr(decl);
31003107

3101-
clang::CXXConstructorDecl *copyCtor = nullptr;
31023108
clang::CXXConstructorDecl *moveCtor = nullptr;
31033109
clang::CXXConstructorDecl *defaultCtor = nullptr;
31043110
if (decl->needsImplicitCopyConstructor() && !isExplicitlyNonCopyable) {
3105-
copyCtor = clangSema.DeclareImplicitCopyConstructor(
3111+
clangSema.DeclareImplicitCopyConstructor(
31063112
const_cast<clang::CXXRecordDecl *>(decl));
31073113
}
31083114
if (decl->needsImplicitMoveConstructor()) {
@@ -3123,10 +3129,7 @@ namespace {
31233129
// Note: we use "doesThisDeclarationHaveABody" here because
31243130
// that's what "DefineImplicitCopyConstructor" checks.
31253131
!declCtor->doesThisDeclarationHaveABody()) {
3126-
if (declCtor->isCopyConstructor()) {
3127-
if (!copyCtor && !isExplicitlyNonCopyable)
3128-
copyCtor = declCtor;
3129-
} else if (declCtor->isMoveConstructor()) {
3132+
if (declCtor->isMoveConstructor()) {
31303133
if (!moveCtor)
31313134
moveCtor = declCtor;
31323135
} else if (declCtor->isDefaultConstructor()) {
@@ -3136,11 +3139,6 @@ namespace {
31363139
}
31373140
}
31383141
}
3139-
if (copyCtor && !isExplicitlyNonCopyable &&
3140-
!decl->isAnonymousStructOrUnion()) {
3141-
clangSema.DefineImplicitCopyConstructor(clang::SourceLocation(),
3142-
copyCtor);
3143-
}
31443142
if (moveCtor && !decl->isAnonymousStructOrUnion()) {
31453143
clangSema.DefineImplicitMoveConstructor(clang::SourceLocation(),
31463144
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)