[cxx-interop] Refactor copyability out of CxxRecordSemantics

This commit is contained in:
susmonteiro
2025-09-12 15:22:29 +01:00
parent a5c6156525
commit 2640c71ec0
5 changed files with 226 additions and 151 deletions

View File

@@ -342,15 +342,9 @@ private:
};
enum class CxxRecordSemanticsKind {
Trivial,
Owned,
MoveOnly,
Value,
Reference,
Iterator,
// A record that is either not copyable/movable or not destructible.
MissingLifetimeOperation,
// A record that has no copy and no move operations
UnavailableConstructors,
// A C++ record that represents a Swift class type exposed to C++ from Swift.
SwiftClassType
};
@@ -576,6 +570,59 @@ private:
void simple_display(llvm::raw_ostream &out, EscapabilityLookupDescriptor desc);
SourceLoc extractNearestSourceLoc(EscapabilityLookupDescriptor desc);
// Swift value semantics of C++ types
// These are usually equivalent, with the exception of references.
// When a reference type is copied, the pointers value is copied rather than
// the objects storage. This means reference types can be imported as
// copyable to Swift, even when they are non-copyable in C++.
enum class CxxValueSemanticsKind {
Copyable,
MoveOnly,
// A record that is either not copyable/movable or not destructible.
MissingLifetimeOperation,
// A record that has no copy and no move operations
UnavailableConstructors,
};
struct CxxValueSemanticsDescriptor final {
const clang::Type *type;
ClangImporter::Implementation *importerImpl;
friend llvm::hash_code hash_value(const CxxValueSemanticsDescriptor &desc) {
return llvm::hash_combine(desc.type);
}
friend bool operator==(const CxxValueSemanticsDescriptor &lhs,
const CxxValueSemanticsDescriptor &rhs) {
return lhs.type == rhs.type;
}
friend bool operator!=(const CxxValueSemanticsDescriptor &lhs,
const CxxValueSemanticsDescriptor &rhs) {
return !(lhs == rhs);
}
};
class CxxValueSemantics
: public SimpleRequest<CxxValueSemantics,
CxxValueSemanticsKind(
CxxValueSemanticsDescriptor),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
bool isCached() const { return true; }
private:
friend SimpleRequest;
CxxValueSemanticsKind evaluate(Evaluator &evaluator,
CxxValueSemanticsDescriptor desc) const;
};
void simple_display(llvm::raw_ostream &out, CxxValueSemanticsDescriptor desc);
SourceLoc extractNearestSourceLoc(CxxValueSemanticsDescriptor desc);
struct CxxDeclExplicitSafetyDescriptor final {
const clang::Decl *decl;
bool isClass;

View File

@@ -45,6 +45,9 @@ SWIFT_REQUEST(ClangImporter, CustomRefCountingOperation,
SWIFT_REQUEST(ClangImporter, ClangTypeEscapability,
CxxEscapability(EscapabilityLookupDescriptor), Cached,
NoLocationInfo)
SWIFT_REQUEST(ClangImporter, CxxValueSemantics,
CxxValueSemanticsKind(CxxValueSemanticsDescriptor), Cached,
NoLocationInfo)
SWIFT_REQUEST(ClangImporter, ClangDeclExplicitSafety,
ExplicitSafety(CxxDeclExplicitSafetyDescriptor), Cached,
NoLocationInfo)

View File

@@ -8094,85 +8094,10 @@ bool importer::isViewType(const clang::CXXRecordDecl *decl) {
return !hasOwnedValueAttr(decl) && hasPointerInSubobjects(decl);
}
static bool copyConstructorIsDefaulted(const clang::CXXRecordDecl *decl) {
auto ctor = llvm::find_if(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
return ctor->isCopyConstructor();
});
assert(ctor != decl->ctor_end());
return ctor->isDefaulted();
}
static bool copyAssignOperatorIsDefaulted(const clang::CXXRecordDecl *decl) {
auto copyAssignOp = llvm::find_if(decl->decls(), [](clang::Decl *member) {
if (auto method = dyn_cast<clang::CXXMethodDecl>(member))
return method->isCopyAssignmentOperator();
return false;
});
assert(copyAssignOp != decl->decls_end());
return cast<clang::CXXMethodDecl>(*copyAssignOp)->isDefaulted();
}
/// Recursively checks that there are no user-provided copy constructors or
/// destructors in any fields or base classes.
/// Does not check C++ records with specific API annotations.
static bool isSufficientlyTrivial(const clang::CXXRecordDecl *decl) {
// Probably a class template that has not yet been specialized:
if (!decl->getDefinition())
return true;
if ((decl->hasUserDeclaredCopyConstructor() &&
!copyConstructorIsDefaulted(decl)) ||
(decl->hasUserDeclaredCopyAssignment() &&
!copyAssignOperatorIsDefaulted(decl)) ||
(decl->hasUserDeclaredDestructor() && decl->getDestructor() &&
!decl->getDestructor()->isDefaulted()))
return false;
auto checkType = [](clang::QualType t) {
if (auto recordType = dyn_cast<clang::RecordType>(t.getCanonicalType())) {
if (auto cxxRecord =
dyn_cast<clang::CXXRecordDecl>(recordType->getDecl())) {
if (hasImportAsRefAttr(cxxRecord) || hasOwnedValueAttr(cxxRecord) ||
hasUnsafeAPIAttr(cxxRecord))
return true;
if (!isSufficientlyTrivial(cxxRecord))
return false;
}
}
return true;
};
for (auto field : decl->fields()) {
if (!checkType(field->getType()))
return false;
}
for (auto base : decl->bases()) {
if (!checkType(base.getType()))
return false;
}
return true;
}
/// Checks if a record provides the required value type lifetime operations
/// (copy and destroy).
static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl) {
// Hack for a base type of std::optional from the Microsoft standard library.
if (decl->isInStdNamespace() && decl->getIdentifier() &&
decl->getName() == "_Optional_construct_base")
return true;
if (decl->hasSimpleCopyConstructor())
return true;
// If we have no way of copying the type we can't import the class
// at all because we cannot express the correct semantics as a swift
// struct.
return llvm::any_of(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
return ctor->isCopyConstructor() && !ctor->isDeleted() &&
!ctor->isIneligibleOrNotSelected() &&
@@ -8183,12 +8108,10 @@ static bool hasCopyTypeOperations(const clang::CXXRecordDecl *decl) {
}
static bool hasMoveTypeOperations(const clang::CXXRecordDecl *decl) {
// If we have no way of copying the type we can't import the class
// at all because we cannot express the correct semantics as a swift
// struct.
if (llvm::any_of(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
return ctor->isMoveConstructor() &&
(ctor->isDeleted() || ctor->getAccess() != clang::AS_public);
(ctor->isDeleted() || ctor->isIneligibleOrNotSelected() ||
ctor->getAccess() != clang::AS_public);
}))
return false;
@@ -8201,7 +8124,8 @@ static bool hasMoveTypeOperations(const clang::CXXRecordDecl *decl) {
static bool hasDestroyTypeOperations(const clang::CXXRecordDecl *decl) {
if (auto dtor = decl->getDestructor()) {
if (dtor->isDeleted() || dtor->getAccess() != clang::AS_public) {
if (dtor->isDeleted() || dtor->isIneligibleOrNotSelected() ||
dtor->getAccess() != clang::AS_public) {
return false;
}
return true;
@@ -8257,49 +8181,17 @@ CxxRecordSemantics::evaluate(Evaluator &evaluator,
auto cxxDecl = dyn_cast<clang::CXXRecordDecl>(decl);
if (!cxxDecl) {
if (hasNonCopyableAttr(decl))
return CxxRecordSemanticsKind::MoveOnly;
return CxxRecordSemanticsKind::Trivial;
return CxxRecordSemanticsKind::Value;
}
if (isSwiftClassType(cxxDecl))
return CxxRecordSemanticsKind::SwiftClassType;
if (!hasDestroyTypeOperations(cxxDecl) ||
(!hasCopyTypeOperations(cxxDecl) && !hasMoveTypeOperations(cxxDecl))) {
if (hasConstructorWithUnsupportedDefaultArgs(cxxDecl))
return CxxRecordSemanticsKind::UnavailableConstructors;
return CxxRecordSemanticsKind::MissingLifetimeOperation;
}
if (hasNonCopyableAttr(cxxDecl) && hasMoveTypeOperations(cxxDecl)) {
return CxxRecordSemanticsKind::MoveOnly;
}
if (hasOwnedValueAttr(cxxDecl)) {
return CxxRecordSemanticsKind::Owned;
}
if (hasIteratorAPIAttr(cxxDecl) || isIterator(cxxDecl)) {
return CxxRecordSemanticsKind::Iterator;
}
if (hasCopyTypeOperations(cxxDecl)) {
return CxxRecordSemanticsKind::Owned;
}
if (hasMoveTypeOperations(cxxDecl)) {
return CxxRecordSemanticsKind::MoveOnly;
}
if (isSufficientlyTrivial(cxxDecl)) {
return CxxRecordSemanticsKind::Trivial;
}
llvm_unreachable("Could not classify C++ type.");
return CxxRecordSemanticsKind::Value;
}
ValueDecl *
@@ -8330,6 +8222,74 @@ CxxRecordAsSwiftType::evaluate(Evaluator &evaluator,
return nullptr;
}
CxxValueSemanticsKind
CxxValueSemantics::evaluate(Evaluator &evaluator,
CxxValueSemanticsDescriptor desc) const {
const auto *type = desc.type;
auto desugared = type->getUnqualifiedDesugaredType();
const auto *recordType = desugared->getAs<clang::RecordType>();
if (!recordType)
return CxxValueSemanticsKind::Copyable;
auto recordDecl = recordType->getDecl();
// When a reference type is copied, the pointers value is copied rather than
// the objects storage. This means reference types can be imported as
// copyable to Swift, even when they are non-copyable in C++.
if (recordHasReferenceSemantics(recordDecl, desc.importerImpl))
return CxxValueSemanticsKind::Copyable;
if (recordDecl->isInStdNamespace()) {
// Hack for a base type of std::optional from the Microsoft standard
// library.
if (recordDecl->getIdentifier() &&
recordDecl->getName() == "_Optional_construct_base")
return CxxValueSemanticsKind::Copyable;
}
const auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl);
if (!cxxRecordDecl) {
if (hasNonCopyableAttr(recordDecl))
return CxxValueSemanticsKind::MoveOnly;
return CxxValueSemanticsKind::Copyable;
}
bool isCopyable = hasCopyTypeOperations(cxxRecordDecl);
bool isMovable = hasMoveTypeOperations(cxxRecordDecl);
if (!hasDestroyTypeOperations(cxxRecordDecl) || (!isCopyable && !isMovable)) {
if (hasConstructorWithUnsupportedDefaultArgs(cxxRecordDecl))
return CxxValueSemanticsKind::UnavailableConstructors;
return CxxValueSemanticsKind::MissingLifetimeOperation;
}
if (hasNonCopyableAttr(cxxRecordDecl) && isMovable)
return CxxValueSemanticsKind::MoveOnly;
if (isCopyable)
return CxxValueSemanticsKind::Copyable;
if (isMovable)
return CxxValueSemanticsKind::MoveOnly;
llvm_unreachable("Could not classify C++ type.");
}
void swift::simple_display(llvm::raw_ostream &out,
CxxValueSemanticsDescriptor desc) {
out << "Checking if '";
out << clang::QualType(desc.type, 0).getAsString();
out << "' is copyable or movable.";
}
SourceLoc swift::extractNearestSourceLoc(CxxValueSemanticsDescriptor) {
return SourceLoc();
}
static bool anySubobjectsSelfContained(const clang::CXXRecordDecl *decl) {
// std::pair and std::tuple might have copy and move constructors, or base
// classes with copy and move constructors, but they are not self-contained

View File

@@ -2080,8 +2080,8 @@ namespace {
bool recordHasMoveOnlySemantics(const clang::RecordDecl *decl) {
auto semanticsKind = evaluateOrDefault(
Impl.SwiftContext.evaluator,
CxxRecordSemantics({decl, Impl.SwiftContext, &Impl}), {});
return semanticsKind == CxxRecordSemanticsKind::MoveOnly;
CxxValueSemantics({decl->getTypeForDecl(), &Impl}), {});
return semanticsKind == CxxValueSemanticsKind::MoveOnly;
}
void markReturnsUnsafeNonescapable(AbstractFunctionDecl *fd) {
@@ -3144,11 +3144,11 @@ namespace {
// It is important that we bail on an unimportable record *before* we import
// any of its members or cache the decl.
auto semanticsKind = evaluateOrDefault(
auto valueSemanticsKind = evaluateOrDefault(
Impl.SwiftContext.evaluator,
CxxRecordSemantics({decl, Impl.SwiftContext, &Impl}), {});
if (semanticsKind == CxxRecordSemanticsKind::MissingLifetimeOperation ||
semanticsKind == CxxRecordSemanticsKind::UnavailableConstructors) {
CxxValueSemantics({decl->getTypeForDecl(), &Impl}), {});
if (valueSemanticsKind == CxxValueSemanticsKind::MissingLifetimeOperation ||
valueSemanticsKind == CxxValueSemanticsKind::UnavailableConstructors) {
HeaderLoc loc(decl->getLocation());
if (hasUnsafeAPIAttr(decl))
@@ -3161,7 +3161,7 @@ namespace {
Impl.diagnose(loc, diag::api_pattern_attr_ignored, "import_iterator",
decl->getNameAsString());
if (semanticsKind == CxxRecordSemanticsKind::UnavailableConstructors) {
if (valueSemanticsKind == CxxValueSemanticsKind::UnavailableConstructors) {
Impl.addImportDiagnostic(
decl, Diagnostic(diag::record_unsupported_default_args),
decl->getLocation());
@@ -3175,7 +3175,12 @@ namespace {
decl->getLocation());
return nullptr;
}
if (semanticsKind == CxxRecordSemanticsKind::SwiftClassType) {
auto cxxRecordsemanticsKind = evaluateOrDefault(
Impl.SwiftContext.evaluator,
CxxRecordSemantics({decl, Impl.SwiftContext, &Impl}), {});
if (cxxRecordsemanticsKind == CxxRecordSemanticsKind::SwiftClassType) {
// FIXME: add a diagnostic here for unsupported imported use of Swift
// type?
return nullptr;
@@ -3411,8 +3416,8 @@ namespace {
decl->getAnonField()->getParent())) {
auto semanticsKind = evaluateOrDefault(
Impl.SwiftContext.evaluator,
CxxRecordSemantics({parent, Impl.SwiftContext, &Impl}), {});
if (semanticsKind == CxxRecordSemanticsKind::MissingLifetimeOperation)
CxxValueSemantics({parent->getTypeForDecl(), &Impl}), {});
if (semanticsKind == CxxValueSemanticsKind::MissingLifetimeOperation)
return nullptr;
}

View File

@@ -2864,6 +2864,71 @@ static void markDeprecated(Decl *decl, llvm::Twine message) {
ctx, ctx.AllocateCopy(message.str())));
}
static bool copyConstructorIsDefaulted(const clang::CXXRecordDecl *decl) {
auto ctor = llvm::find_if(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
return ctor->isCopyConstructor();
});
assert(ctor != decl->ctor_end());
return ctor->isDefaulted();
}
static bool copyAssignOperatorIsDefaulted(const clang::CXXRecordDecl *decl) {
auto copyAssignOp = llvm::find_if(decl->decls(), [](clang::Decl *member) {
if (auto method = dyn_cast<clang::CXXMethodDecl>(member))
return method->isCopyAssignmentOperator();
return false;
});
assert(copyAssignOp != decl->decls_end());
return cast<clang::CXXMethodDecl>(*copyAssignOp)->isDefaulted();
}
/// Recursively checks that there are no user-provided copy constructors or
/// destructors in any fields or base classes.
/// Does not check C++ records with specific API annotations.
static bool isSufficientlyTrivial(const clang::CXXRecordDecl *decl) {
// Probably a class template that has not yet been specialized:
if (!decl->getDefinition())
return true;
if ((decl->hasUserDeclaredCopyConstructor() &&
!copyConstructorIsDefaulted(decl)) ||
(decl->hasUserDeclaredCopyAssignment() &&
!copyAssignOperatorIsDefaulted(decl)) ||
(decl->hasUserDeclaredDestructor() && decl->getDestructor() &&
!decl->getDestructor()->isDefaulted()))
return false;
auto checkType = [](clang::QualType t) {
if (auto recordType = dyn_cast<clang::RecordType>(t.getCanonicalType())) {
if (auto cxxRecord =
dyn_cast<clang::CXXRecordDecl>(recordType->getDecl())) {
if (hasImportAsRefAttr(cxxRecord) || hasOwnedValueAttr(cxxRecord) ||
hasUnsafeAPIAttr(cxxRecord))
return true;
if (!isSufficientlyTrivial(cxxRecord))
return false;
}
}
return true;
};
for (auto field : decl->fields()) {
if (!checkType(field->getType()))
return false;
}
for (auto base : decl->bases()) {
if (!checkType(base.getType()))
return false;
}
return true;
}
/// Find an explicitly-provided "destroy" operation specified for the
/// given Clang type and return it.
FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
@@ -2943,14 +3008,23 @@ FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
// If this type isn't imported as noncopyable, we can't respect the request
// for a destroy operation.
ASTContext &ctx = ImporterImpl.SwiftContext;
auto semanticsKind = evaluateOrDefault(
ctx.evaluator,
CxxRecordSemantics({clangType, ctx, &ImporterImpl}), {});
switch (semanticsKind) {
case CxxRecordSemanticsKind::Owned:
auto valueSemanticsKind = evaluateOrDefault(
ctx.evaluator,
CxxValueSemantics({clangType->getTypeForDecl(), &ImporterImpl}), {});
if (valueSemanticsKind == CxxValueSemanticsKind::MoveOnly)
return destroyFunc;
if (valueSemanticsKind != CxxValueSemanticsKind::Copyable)
return nullptr;
auto cxxRecordSemanticsKind = evaluateOrDefault(
ctx.evaluator, CxxRecordSemantics({clangType, ctx, &ImporterImpl}), {});
switch (cxxRecordSemanticsKind) {
case CxxRecordSemanticsKind::Value:
case CxxRecordSemanticsKind::Reference:
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(clangType)) {
if (!cxxRecord->hasTrivialDestructor()) {
if (!isSufficientlyTrivial(cxxRecord)) {
markDeprecated(
nominal,
"destroy operation '" +
@@ -2960,9 +3034,6 @@ FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
}
}
LLVM_FALLTHROUGH;
case CxxRecordSemanticsKind::Trivial:
markDeprecated(
nominal,
"destroy operation '" +
@@ -2972,20 +3043,9 @@ FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
return nullptr;
case CxxRecordSemanticsKind::Iterator:
case CxxRecordSemanticsKind::MissingLifetimeOperation:
case CxxRecordSemanticsKind::SwiftClassType:
return nullptr;
case CxxRecordSemanticsKind::MoveOnly:
case CxxRecordSemanticsKind::UnavailableConstructors:
// Handled below.
break;
}
if (semanticsKind != CxxRecordSemanticsKind::MoveOnly) {
return nullptr;
}
return destroyFunc;
}
/// Function body synthesizer for a deinit of a noncopyable type, which