Skip to content

Commit a07da0f

Browse files
committed
[cxx-interop] Avoid trying to instantiate copy constructors of explicitly non-copyable structs
If a C++ struct is annotated with `__attribute__((swift_attr("~Copyable")))`, Swift should not try to instantiate the copy constructor of the struct. This provides an escape hatch for C++ types that are designed to be non-copyable, but do not explicitly define a deleted copy constructor, and instead trigger complex template instantiation failures when a copy is attempted. rdar://157034491
1 parent ba88078 commit a07da0f

8 files changed

+67
-5
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8008,7 +8008,7 @@ bool importer::hasIteratorAPIAttr(const clang::Decl *decl) {
80088008
return hasSwiftAttribute(decl, "import_iterator");
80098009
}
80108010

8011-
static bool hasNonCopyableAttr(const clang::RecordDecl *decl) {
8011+
bool importer::hasNonCopyableAttr(const clang::RecordDecl *decl) {
80128012
return hasSwiftAttribute(decl, "~Copyable");
80138013
}
80148014

lib/ClangImporter/ImportDecl.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3002,10 +3002,14 @@ namespace {
30023002
clangSema.DefineImplicitDefaultConstructor(clang::SourceLocation(),
30033003
ctor);
30043004
}
3005+
// If the C++ struct is annotated as non-copyable, we should not try to
3006+
// instantiate its copy constructor.
3007+
bool isExplicitlyNonCopyable = hasNonCopyableAttr(decl);
3008+
30053009
clang::CXXConstructorDecl *copyCtor = nullptr;
30063010
clang::CXXConstructorDecl *moveCtor = nullptr;
30073011
clang::CXXConstructorDecl *defaultCtor = nullptr;
3008-
if (decl->needsImplicitCopyConstructor()) {
3012+
if (decl->needsImplicitCopyConstructor() && !isExplicitlyNonCopyable) {
30093013
copyCtor = clangSema.DeclareImplicitCopyConstructor(
30103014
const_cast<clang::CXXRecordDecl *>(decl));
30113015
}
@@ -3028,7 +3032,7 @@ namespace {
30283032
// that's what "DefineImplicitCopyConstructor" checks.
30293033
!declCtor->doesThisDeclarationHaveABody()) {
30303034
if (declCtor->isCopyConstructor()) {
3031-
if (!copyCtor)
3035+
if (!copyCtor && !isExplicitlyNonCopyable)
30323036
copyCtor = declCtor;
30333037
} else if (declCtor->isMoveConstructor()) {
30343038
if (!moveCtor)
@@ -3040,7 +3044,7 @@ namespace {
30403044
}
30413045
}
30423046
}
3043-
if (copyCtor) {
3047+
if (copyCtor && !isExplicitlyNonCopyable) {
30443048
clangSema.DefineImplicitCopyConstructor(clang::SourceLocation(),
30453049
copyCtor);
30463050
}

lib/ClangImporter/ImporterImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2137,7 +2137,7 @@ bool hasUnsafeAPIAttr(const clang::Decl *decl);
21372137
bool hasIteratorAPIAttr(const clang::Decl *decl);
21382138

21392139
bool hasNonEscapableAttr(const clang::RecordDecl *decl);
2140-
2140+
bool hasNonCopyableAttr(const clang::RecordDecl *decl);
21412141
bool hasEscapableAttr(const clang::RecordDecl *decl);
21422142

21432143
bool isViewType(const clang::CXXRecordDecl *decl);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,8 @@ module ManySpecializations {
172172
header "many-specializations.h"
173173
requires cplusplus
174174
}
175+
176+
module UninstantiatableSpecialMembers {
177+
header "uninstantiatable-special-members.h"
178+
requires cplusplus
179+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
template <class T>
2+
struct __attribute__((swift_attr("~Copyable"))) HasUninstantiatableCopyConstructor {
3+
template <class U>
4+
void scaryPoison(U u) {
5+
U::doesNotExist(u);
6+
}
7+
8+
int value;
9+
HasUninstantiatableCopyConstructor(int value) : value(value) {}
10+
HasUninstantiatableCopyConstructor(
11+
const HasUninstantiatableCopyConstructor &other) {
12+
scaryPoison(other);
13+
}
14+
HasUninstantiatableCopyConstructor(
15+
HasUninstantiatableCopyConstructor &&other) = default;
16+
};
17+
18+
typedef HasUninstantiatableCopyConstructor<int> NonCopyableInst;
19+
20+
template <class T>
21+
struct __attribute__((swift_attr("~Copyable"))) DerivedUninstantiatableCopyConstructor : HasUninstantiatableCopyConstructor<T> {
22+
DerivedUninstantiatableCopyConstructor(int value) : HasUninstantiatableCopyConstructor<T>(value) {}
23+
DerivedUninstantiatableCopyConstructor(const DerivedUninstantiatableCopyConstructor &other) = default;
24+
DerivedUninstantiatableCopyConstructor(DerivedUninstantiatableCopyConstructor &&other) = default;
25+
};
26+
27+
typedef DerivedUninstantiatableCopyConstructor<int> DerivedNonCopyableInst;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// RUN: %target-swift-emit-irgen %s -cxx-interoperability-mode=default -I %S/Inputs | %FileCheck %s
2+
3+
import UninstantiatableSpecialMembers
4+
5+
let nonCopyableValue = NonCopyableInst(123)
6+
let nonCopyableDerived = DerivedNonCopyableInst(567)
7+
8+
// CHECK-NOT: scaryPoison
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=UninstantiatableSpecialMembers -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift | %FileCheck %s
2+
3+
// CHECK: struct HasUninstantiatableCopyConstructor<CInt> {
4+
// CHECK: }
5+
// CHECK: typealias NonCopyableInst = HasUninstantiatableCopyConstructor<CInt>
6+
7+
// CHECK: struct DerivedUninstantiatableCopyConstructor<CInt> {
8+
// CHECK: }
9+
// CHECK: typealias DerivedNonCopyableInst = DerivedUninstantiatableCopyConstructor<CInt>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %target-typecheck-verify-swift -cxx-interoperability-mode=default -I %S/Inputs
2+
3+
import UninstantiatableSpecialMembers
4+
5+
let nonCopyableValue = NonCopyableInst(123)
6+
let copy1 = copy nonCopyableValue // expected-error {{'copy' cannot be applied to noncopyable types}}
7+
8+
let nonCopyableDerived = DerivedNonCopyableInst(567)
9+
let copy2 = copy nonCopyableDerived // expected-error {{'copy' cannot be applied to noncopyable types}}

0 commit comments

Comments
 (0)