From 21b9b9f71323f0f4cb625d939c4b1a91304ed380 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 27 Jul 2025 19:47:07 -0700 Subject: [PATCH 1/9] [Clang importer] Allow C structs to be noncopyable, too In C interoperability mode, respect the ~Copyable annotation on C structs to import them as ~Copyable types. Fixes rdar://156877772. --- lib/ClangImporter/ClangImporter.cpp | 3 ++ test/Interop/C/struct/Inputs/module.modulemap | 4 +++ .../C/struct/Inputs/noncopyable-struct.h | 5 ++++ .../C/struct/noncopyable_structs.swift | 29 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 test/Interop/C/struct/Inputs/noncopyable-struct.h create mode 100644 test/Interop/C/struct/noncopyable_structs.swift diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index f3f6a446cd3e2..de466879e09a8 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -8232,6 +8232,9 @@ CxxRecordSemantics::evaluate(Evaluator &evaluator, auto cxxDecl = dyn_cast(decl); if (!cxxDecl) { + if (hasNonCopyableAttr(decl)) + return CxxRecordSemanticsKind::MoveOnly; + return CxxRecordSemanticsKind::Trivial; } diff --git a/test/Interop/C/struct/Inputs/module.modulemap b/test/Interop/C/struct/Inputs/module.modulemap index 641158b9978ef..ba61a05b26407 100644 --- a/test/Interop/C/struct/Inputs/module.modulemap +++ b/test/Interop/C/struct/Inputs/module.modulemap @@ -10,3 +10,7 @@ module ForeignReference { module StructAsOptionSet { header "struct-as-option-set.h" } + +module NoncopyableStructs { + header "noncopyable-struct.h" +} \ No newline at end of file diff --git a/test/Interop/C/struct/Inputs/noncopyable-struct.h b/test/Interop/C/struct/Inputs/noncopyable-struct.h new file mode 100644 index 0000000000000..14a96c396f86d --- /dev/null +++ b/test/Interop/C/struct/Inputs/noncopyable-struct.h @@ -0,0 +1,5 @@ +// RUN: %target-typecheck-verify-swift -I %S/Inputs/ + +typedef struct __attribute__((swift_attr("~Copyable"))) NonCopyable { + float x, y; +} NonCopyable; diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift new file mode 100644 index 0000000000000..c1e3ca8bcf64d --- /dev/null +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -0,0 +1,29 @@ + +// Check that we get the expected errors for incorrect uses of noncopyable +// imported types with both C and C++ interoperability. + +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -cxx-interoperability-mode=default + +// Check that we get the expected IR + +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck %s +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck %s + +import NoncopyableStructs + +// CHECK-LABEL: define hidden swiftcc void @"$s19noncopyable_structs9consumeNCyySo11NonCopyableVnF"(float %0, float %1) #0 { +// CHECK: call ptr @"$sSo11NonCopyableVWOh" +// CHECK: define linkonce_odr hidden ptr @"$sSo11NonCopyableVWOh"(ptr %0) +// CHECK-NEXT: entry: +// CHECK-NEXT: ret ptr +func consumeNC(_ nc: consuming NonCopyable) { } + +func testNC() { + let nc = NonCopyable() // expected-error{{'nc' consumed more than once}} + consumeNC(nc) // expected-note{{consumed here}} + + #if ERRORS + consumeNC(nc) // expected-note{{consumed again here}} + #endif +} From 6ba560fb4b65bda56a6992900c748fe093637a6c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 00:02:31 -0700 Subject: [PATCH 2/9] [Clang importer] Allow noncopyable C structs to define "destroy" operation A C struct can be imported as noncopyable, but C doesn't have destructors, so there is no way to provide user-defined logic to perform the destruction. Introduce a new swift_attr that applies to imported noncopyable types and which provides such a "destroy" operation. It can be used like this: typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:wgpuAdapterInfoFreeMembers"))) WGPUAdapterInfo { /*...*/ } WGPUAdapterInfo; void wgpuAdapterInfoFreeMembers(WGPUAdapterInfo adapterInfo); This will bring the WGPUAdapterInfo struct in as a noncopyable type that will be cleaned up by calling wgpuAdapterInfoFreeMembers once it is no longer in use. Implements rdar://156889370. --- lib/ClangImporter/ClangImporter.cpp | 5 +- lib/ClangImporter/ImportDecl.cpp | 93 +++++++++++++++++++ lib/ClangImporter/ImporterImpl.h | 8 ++ lib/IRGen/GenStruct.cpp | 31 +++++-- lib/SILGen/SILGen.h | 5 + lib/SILGen/SILGenLazyConformance.cpp | 7 ++ lib/SILGen/SILGenType.cpp | 7 ++ .../C/struct/Inputs/noncopyable-struct.h | 14 +++ .../C/struct/noncopyable_structs.swift | 46 +++++++-- 9 files changed, 198 insertions(+), 18 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index de466879e09a8..88fd32edc5972 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -7887,8 +7887,9 @@ getRefParentDecls(const clang::RecordDecl *decl, ASTContext &ctx, return matchingDecls; } -static llvm::SmallVector -getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name) { +llvm::SmallVector +importer::getValueDeclsForName( + const clang::Decl *decl, ASTContext &ctx, StringRef name) { llvm::SmallVector results; auto *clangMod = decl->getOwningModule(); if (clangMod && clangMod->isSubModule()) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 5d45b9a419a24..18cbdf6b36c50 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2687,10 +2687,103 @@ namespace { } } + // A type imported as noncopyable, check whether an explicit deinit + // should be introduced to call a user-defined "destroy" function. + if (recordHasMoveOnlySemantics(decl)) { + addExplicitDeinitIfRequired(result, decl); + } + result->setMemberLoader(&Impl, 0); return result; } + /// Find an explicitly-provided "destroy" operation specified for the + /// given Clang type and return it. + static FuncDecl *findExplicitDestroy( + NominalTypeDecl *nominal, const clang::TypeDecl *clangType) { + if (!clangType->hasAttrs()) + return nullptr; + + llvm::TinyPtrVector matchingDestroyFuncs; + for (auto attr : clangType->getAttrs()) { + auto swiftAttr = dyn_cast(attr); + if (!swiftAttr) + continue; + + auto attributeName = swiftAttr->getAttribute(); + if (!attributeName.starts_with("destroy:")) + continue; + + auto destroyFuncName = attributeName.drop_front(strlen("destroy:")); + auto decls = getValueDeclsForName( + clangType, nominal->getASTContext(), destroyFuncName); + for (auto decl : decls) { + auto func = dyn_cast(decl); + if (!func) + continue; + + auto params = func->getParameters(); + if (params->size() != 1) + continue; + + if (!params->get(0)->getInterfaceType()->isEqual( + nominal->getDeclaredInterfaceType())) + continue; + + matchingDestroyFuncs.push_back(func); + } + } + + if (matchingDestroyFuncs.size() == 1) + return matchingDestroyFuncs[0]; + + return nullptr; + } + + /// Function body synthesizer for a deinit of a noncopyable type, which + /// passes "self" to the given "destroy" function. + static std::pair + synthesizeDeinitBodyForCustomDestroy( + AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) { + auto deinit = cast(deinitFunc); + auto destroyFunc = static_cast(opaqueDestroyFunc); + + ASTContext &ctx = deinit->getASTContext(); + auto funcRef = new (ctx) DeclRefExpr( + destroyFunc, DeclNameLoc(), /*Implicit=*/true); + auto selfRef = new (ctx) DeclRefExpr( + deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true); + auto callExpr = CallExpr::createImplicit( + ctx, funcRef, + ArgumentList::createImplicit( + ctx, + { Argument(SourceLoc(), Identifier(), selfRef)} + ) + ); + + auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) }); + return std::make_pair(braceStmt, /*typechecked=*/false); + } + + /// For a type that is imported as noncopyable, look for an + /// explicitly-provided "destroy" operation. If present, introduce a deinit + /// that calls it. + void addExplicitDeinitIfRequired( + NominalTypeDecl *nominal, const clang::TypeDecl *clangType) { + auto destroyFunc = findExplicitDestroy(nominal, clangType); + if (!destroyFunc) + return; + + ASTContext &ctx = Impl.SwiftContext; + auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal); + destructor->setSynthesized(true); + destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true); + destructor->setBodySynthesizer( + synthesizeDeinitBodyForCustomDestroy, destroyFunc); + + nominal->addMember(destructor); + } + void validatePrivateFileIDAttributes(const clang::CXXRecordDecl *decl) { auto anns = importer::getPrivateFileIDAttrs(decl); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 84ea4abebba77..236dd8aa18918 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -2179,6 +2179,14 @@ inline clang::QualType desugarIfBoundsAttributed(clang::QualType type) { /// If \a type is an elaborated type, it should be desugared first. ImportedType findOptionSetEnum(clang::QualType type, ClangImporter::Implementation &Impl); + +/// Find value declarations in the same module as the given Clang declaration +/// and with the given name. +/// +/// The name we're looking for is the Swift name. +llvm::SmallVector +getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name); + } // end namespace importer } // end namespace swift diff --git a/lib/IRGen/GenStruct.cpp b/lib/IRGen/GenStruct.cpp index 1b7f71dd99dc3..90b72aec9de03 100644 --- a/lib/IRGen/GenStruct.cpp +++ b/lib/IRGen/GenStruct.cpp @@ -374,12 +374,14 @@ namespace { unsigned explosionSize, llvm::Type *storageType, Size size, SpareBitVector &&spareBits, Alignment align, + IsTriviallyDestroyable_t isTriviallyDestroyable, + IsCopyable_t isCopyable, const clang::RecordDecl *clangDecl) : StructTypeInfoBase(StructTypeInfoKind::LoadableClangRecordTypeInfo, fields, explosionSize, FieldsAreABIAccessible, storageType, size, std::move(spareBits), align, - IsTriviallyDestroyable, - IsCopyable, + isTriviallyDestroyable, + isCopyable, IsFixedSize, IsABIAccessible), ClangDecl(clangDecl) {} @@ -469,6 +471,7 @@ namespace { AddressOnlyPointerAuthRecordTypeInfo(ArrayRef fields, llvm::Type *storageType, Size size, Alignment align, + IsCopyable_t isCopyable, const clang::RecordDecl *clangDecl) : StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo, fields, FieldsAreABIAccessible, storageType, size, @@ -477,7 +480,7 @@ namespace { SpareBitVector(std::optional{ llvm::APInt(size.getValueInBits(), 0)}), align, IsNotTriviallyDestroyable, - IsNotBitwiseTakable, IsCopyable, IsFixedSize, + IsNotBitwiseTakable, isCopyable, IsFixedSize, IsABIAccessible), clangDecl(clangDecl) { (void)clangDecl; @@ -645,6 +648,7 @@ namespace { AddressOnlyCXXClangRecordTypeInfo(ArrayRef fields, llvm::Type *storageType, Size size, Alignment align, + IsCopyable_t isCopyable, const clang::RecordDecl *clangDecl) : StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo, fields, FieldsAreABIAccessible, storageType, size, @@ -654,9 +658,7 @@ namespace { llvm::APInt(size.getValueInBits(), 0)}), align, IsNotTriviallyDestroyable, IsNotBitwiseTakable, - // TODO: Set this appropriately for the type's - // C++ import behavior. - IsCopyable, IsFixedSize, IsABIAccessible), + isCopyable, IsFixedSize, IsABIAccessible), ClangDecl(clangDecl) { (void)ClangDecl; } @@ -1342,15 +1344,26 @@ class ClangRecordLowering { llvmType->setBody(LLVMFields, /*packed*/ true); if (SwiftType.getStructOrBoundGenericStruct()->isCxxNonTrivial()) { return AddressOnlyCXXClangRecordTypeInfo::create( - FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl); + FieldInfos, llvmType, TotalStride, TotalAlignment, + (SwiftDecl && !SwiftDecl->canBeCopyable()) + ? IsNotCopyable : IsCopyable, + ClangDecl); } if (SwiftType.getStructOrBoundGenericStruct()->isNonTrivialPtrAuth()) { return AddressOnlyPointerAuthRecordTypeInfo::create( - FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl); + FieldInfos, llvmType, TotalStride, TotalAlignment, + (SwiftDecl && !SwiftDecl->canBeCopyable()) + ? IsNotCopyable : IsCopyable, + ClangDecl); } return LoadableClangRecordTypeInfo::create( FieldInfos, NextExplosionIndex, llvmType, TotalStride, - std::move(SpareBits), TotalAlignment, ClangDecl); + std::move(SpareBits), TotalAlignment, + (SwiftDecl && SwiftDecl->getValueTypeDestructor()) + ? IsNotTriviallyDestroyable : IsTriviallyDestroyable, + (SwiftDecl && !SwiftDecl->canBeCopyable()) + ? IsNotCopyable : IsCopyable, + ClangDecl); } private: diff --git a/lib/SILGen/SILGen.h b/lib/SILGen/SILGen.h index 6b75a571c2d89..2e96cce09b59c 100644 --- a/lib/SILGen/SILGen.h +++ b/lib/SILGen/SILGen.h @@ -84,6 +84,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { /// Set of delayed conformances that have already been forced. llvm::DenseSet forcedConformances; + /// Imported noncopyable types that we have seen. + llvm::DenseSet importedNontrivialNoncopyableTypes; + size_t anonymousSymbolCounter = 0; std::optional StringToNSStringFn; @@ -282,6 +285,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { void visitMacroDecl(MacroDecl *d); void visitMacroExpansionDecl(MacroExpansionDecl *d); + void visitImportedNontrivialNoncopyableType(NominalTypeDecl *nominal); + // Same as AbstractStorageDecl::visitEmittedAccessors, but skips over skipped // (unavailable) decls. void visitEmittedAccessors(AbstractStorageDecl *D, diff --git a/lib/SILGen/SILGenLazyConformance.cpp b/lib/SILGen/SILGenLazyConformance.cpp index 90cc4772a9219..31674443b117f 100644 --- a/lib/SILGen/SILGenLazyConformance.cpp +++ b/lib/SILGen/SILGenLazyConformance.cpp @@ -110,6 +110,13 @@ void SILGenModule::useConformancesFromType(CanType type) { if (isa(decl)) return; + // If this is an imported noncopyable type with a deinitializer, record it. + if (decl->hasClangNode() && !decl->canBeCopyable() && + decl->getValueTypeDestructor() && + importedNontrivialNoncopyableTypes.insert(decl).second) { + visitImportedNontrivialNoncopyableType(decl); + } + auto genericSig = decl->getGenericSignature(); if (!genericSig) return; diff --git a/lib/SILGen/SILGenType.cpp b/lib/SILGen/SILGenType.cpp index f89955564a145..be1bef8a79098 100644 --- a/lib/SILGen/SILGenType.cpp +++ b/lib/SILGen/SILGenType.cpp @@ -1577,6 +1577,13 @@ void SILGenModule::visitNominalTypeDecl(NominalTypeDecl *ntd) { SILGenType(*this, ntd).emitType(); } +void SILGenModule::visitImportedNontrivialNoncopyableType( + NominalTypeDecl *nominal) { + emitNonCopyableTypeDeinitTable(nominal); + SILGenType(*this, nominal) + .visitDestructorDecl(nominal->getValueTypeDestructor()); +} + /// SILGenExtension - an ASTVisitor for generating SIL from method declarations /// and protocol conformances inside type extensions. class SILGenExtension : public TypeMemberVisitor { diff --git a/test/Interop/C/struct/Inputs/noncopyable-struct.h b/test/Interop/C/struct/Inputs/noncopyable-struct.h index 14a96c396f86d..b678c3bd702c8 100644 --- a/test/Interop/C/struct/Inputs/noncopyable-struct.h +++ b/test/Interop/C/struct/Inputs/noncopyable-struct.h @@ -3,3 +3,17 @@ typedef struct __attribute__((swift_attr("~Copyable"))) NonCopyable { float x, y; } NonCopyable; + +typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:freeNonCopyableWithDeinit"))) NonCopyableWithDeinit { + void *storage; +} NonCopyableWithDeinit; + +#ifdef __cplusplus +extern "C" { +#endif + +void freeNonCopyableWithDeinit(NonCopyableWithDeinit ncd); + +#ifdef __cplusplus +} +#endif diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index c1e3ca8bcf64d..48fa370a4a93f 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -5,18 +5,22 @@ // RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS // RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -cxx-interoperability-mode=default +// Check that we get the expected SIL +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-SIL %s +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck -check-prefix CHECK-SIL %s + // Check that we get the expected IR -// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck %s -// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck %s +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-IR %s +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default | %FileCheck -check-prefix CHECK-IR %s import NoncopyableStructs -// CHECK-LABEL: define hidden swiftcc void @"$s19noncopyable_structs9consumeNCyySo11NonCopyableVnF"(float %0, float %1) #0 { -// CHECK: call ptr @"$sSo11NonCopyableVWOh" -// CHECK: define linkonce_odr hidden ptr @"$sSo11NonCopyableVWOh"(ptr %0) -// CHECK-NEXT: entry: -// CHECK-NEXT: ret ptr +// CHECK-IR-LABEL: define hidden swiftcc void @"$s19noncopyable_structs9consumeNCyySo11NonCopyableVnF"(float %0, float %1) #0 { +// CHECK-IR: call ptr @"$sSo11NonCopyableVWOh" +// CHECK-IR: define linkonce_odr hidden ptr @"$sSo11NonCopyableVWOh"(ptr %0) +// CHECK-IR-NEXT: entry: +// CHECK-IR-NEXT: ret ptr func consumeNC(_ nc: consuming NonCopyable) { } func testNC() { @@ -27,3 +31,31 @@ func testNC() { consumeNC(nc) // expected-note{{consumed again here}} #endif } + +func consumeNCWithDeinit(_ nc: consuming NonCopyableWithDeinit) { } + +func testNCWithDeinit() { + let nc = NonCopyableWithDeinit() // expected-error{{'nc' consumed more than once}} + consumeNCWithDeinit(nc) // expected-note{{consumed here}} + + #if ERRORS + consumeNCWithDeinit(nc) // expected-note{{consumed again here}} + #endif +} + +// CHECK-SIL: sil shared @$sSo21NonCopyableWithDeinitVfD : $@convention(method) (@owned NonCopyableWithDeinit) -> () { +// CHECK-SIL: bb0([[SELF:%[0-9]+]] : $NonCopyableWithDeinit): +// CHECK-SIL: [[SELF_ALLOC:%[0-9]+]] = alloc_stack $NonCopyableWithDeinit +// CHECK-SIL: store [[SELF]] to [[SELF_ALLOC]] +// CHECK-SIL: [[SELF_RELOAD:%[0-9]+]] = load [[SELF_ALLOC]] +// CHECK-SIL: [[FN:%[0-9]+]] = function_ref @{{.*}}freeNonCopyableWithDeinit : $@convention(c) (NonCopyableWithDeinit) -> () +// CHECK-SIL: apply [[FN]]([[SELF_RELOAD]]) : $@convention(c) (NonCopyableWithDeinit) -> () + +// CHECK-IR-LABEL: define hidden swiftcc void @"$s19noncopyable_structs19consumeNCWithDeinityySo015NonCopyableWithE0VnF" +// CHECK-IR: call swiftcc void @"$sSo21NonCopyableWithDeinitVfD" + +// CHECK-IR-LABEL: define {{.*}} swiftcc void @"$sSo21NonCopyableWithDeinitVfD" +// CHECK-IR: {{(call|invoke)}} void @{{.*}}freeNonCopyableWithDeinit + +// CHECK-SIL-LABEL: sil_moveonlydeinit NonCopyableWithDeinit { +// CHECK-SIL: @$sSo21NonCopyableWithDeinitVfD From 0821009c34cb1df49f6311e8409273a672924ba2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 08:30:49 -0700 Subject: [PATCH 3/9] Fix test for Windows and add missing newline to header --- test/Interop/C/struct/Inputs/module.modulemap | 2 +- test/Interop/C/struct/noncopyable_structs.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Interop/C/struct/Inputs/module.modulemap b/test/Interop/C/struct/Inputs/module.modulemap index ba61a05b26407..82698ffd42c87 100644 --- a/test/Interop/C/struct/Inputs/module.modulemap +++ b/test/Interop/C/struct/Inputs/module.modulemap @@ -13,4 +13,4 @@ module StructAsOptionSet { module NoncopyableStructs { header "noncopyable-struct.h" -} \ No newline at end of file +} diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index 48fa370a4a93f..553187c3c4b59 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -48,7 +48,7 @@ func testNCWithDeinit() { // CHECK-SIL: [[SELF_ALLOC:%[0-9]+]] = alloc_stack $NonCopyableWithDeinit // CHECK-SIL: store [[SELF]] to [[SELF_ALLOC]] // CHECK-SIL: [[SELF_RELOAD:%[0-9]+]] = load [[SELF_ALLOC]] -// CHECK-SIL: [[FN:%[0-9]+]] = function_ref @{{.*}}freeNonCopyableWithDeinit : $@convention(c) (NonCopyableWithDeinit) -> () +// CHECK-SIL: [[FN:%[0-9]+]] = function_ref @{{.*}}freeNonCopyableWithDeinit{{.*}} : $@convention(c) (NonCopyableWithDeinit) -> () // CHECK-SIL: apply [[FN]]([[SELF_RELOAD]]) : $@convention(c) (NonCopyableWithDeinit) -> () // CHECK-IR-LABEL: define hidden swiftcc void @"$s19noncopyable_structs19consumeNCWithDeinityySo015NonCopyableWithE0VnF" From 63135e89291f58932ed48d87fdacdb16dca35160 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 13:52:50 -0700 Subject: [PATCH 4/9] [Clang importer] Diagnose various issues with the new "destroy:" When we cannot respect the "destroy:" annotation, mark the type as deprecated with a message thst says why there is a problem. There are various potential problems: * Multiple conflicting destroy functions * Destroy functions that don't meet the pattern * Type isn't imported as a move-only type * Type has a non-trivial destructor (in C++) --- lib/ClangImporter/ImportDecl.cpp | 94 +--------- lib/ClangImporter/SwiftDeclSynthesizer.cpp | 174 ++++++++++++++++++ lib/ClangImporter/SwiftDeclSynthesizer.h | 12 ++ .../C/struct/Inputs/noncopyable-struct.h | 32 ++++ .../C/struct/noncopyable_structs.swift | 24 ++- 5 files changed, 242 insertions(+), 94 deletions(-) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 18cbdf6b36c50..d94f664ea8246 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2687,103 +2687,13 @@ namespace { } } - // A type imported as noncopyable, check whether an explicit deinit - // should be introduced to call a user-defined "destroy" function. - if (recordHasMoveOnlySemantics(decl)) { - addExplicitDeinitIfRequired(result, decl); - } + // If we need it, add an explicit "deinit" to this type. + synthesizer.addExplicitDeinitIfRequired(result, decl); result->setMemberLoader(&Impl, 0); return result; } - /// Find an explicitly-provided "destroy" operation specified for the - /// given Clang type and return it. - static FuncDecl *findExplicitDestroy( - NominalTypeDecl *nominal, const clang::TypeDecl *clangType) { - if (!clangType->hasAttrs()) - return nullptr; - - llvm::TinyPtrVector matchingDestroyFuncs; - for (auto attr : clangType->getAttrs()) { - auto swiftAttr = dyn_cast(attr); - if (!swiftAttr) - continue; - - auto attributeName = swiftAttr->getAttribute(); - if (!attributeName.starts_with("destroy:")) - continue; - - auto destroyFuncName = attributeName.drop_front(strlen("destroy:")); - auto decls = getValueDeclsForName( - clangType, nominal->getASTContext(), destroyFuncName); - for (auto decl : decls) { - auto func = dyn_cast(decl); - if (!func) - continue; - - auto params = func->getParameters(); - if (params->size() != 1) - continue; - - if (!params->get(0)->getInterfaceType()->isEqual( - nominal->getDeclaredInterfaceType())) - continue; - - matchingDestroyFuncs.push_back(func); - } - } - - if (matchingDestroyFuncs.size() == 1) - return matchingDestroyFuncs[0]; - - return nullptr; - } - - /// Function body synthesizer for a deinit of a noncopyable type, which - /// passes "self" to the given "destroy" function. - static std::pair - synthesizeDeinitBodyForCustomDestroy( - AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) { - auto deinit = cast(deinitFunc); - auto destroyFunc = static_cast(opaqueDestroyFunc); - - ASTContext &ctx = deinit->getASTContext(); - auto funcRef = new (ctx) DeclRefExpr( - destroyFunc, DeclNameLoc(), /*Implicit=*/true); - auto selfRef = new (ctx) DeclRefExpr( - deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true); - auto callExpr = CallExpr::createImplicit( - ctx, funcRef, - ArgumentList::createImplicit( - ctx, - { Argument(SourceLoc(), Identifier(), selfRef)} - ) - ); - - auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) }); - return std::make_pair(braceStmt, /*typechecked=*/false); - } - - /// For a type that is imported as noncopyable, look for an - /// explicitly-provided "destroy" operation. If present, introduce a deinit - /// that calls it. - void addExplicitDeinitIfRequired( - NominalTypeDecl *nominal, const clang::TypeDecl *clangType) { - auto destroyFunc = findExplicitDestroy(nominal, clangType); - if (!destroyFunc) - return; - - ASTContext &ctx = Impl.SwiftContext; - auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal); - destructor->setSynthesized(true); - destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true); - destructor->setBodySynthesizer( - synthesizeDeinitBodyForCustomDestroy, destroyFunc); - - nominal->addMember(destructor); - } - void validatePrivateFileIDAttributes(const clang::CXXRecordDecl *decl) { auto anns = importer::getPrivateFileIDAttrs(decl); diff --git a/lib/ClangImporter/SwiftDeclSynthesizer.cpp b/lib/ClangImporter/SwiftDeclSynthesizer.cpp index 6deac6fcc741e..95dafed1fd7ac 100644 --- a/lib/ClangImporter/SwiftDeclSynthesizer.cpp +++ b/lib/ClangImporter/SwiftDeclSynthesizer.cpp @@ -23,6 +23,7 @@ #include "swift/AST/Stmt.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Assertions.h" +#include "swift/ClangImporter/ClangImporterRequests.h" #include "clang/AST/Mangle.h" #include "clang/Sema/DelayedDiagnostic.h" @@ -2809,6 +2810,179 @@ synthesizeAvailabilityDomainPredicateBody(AbstractFunctionDecl *afd, return {body, /*isTypeChecked=*/false}; } +/// Mark the given declaration as always deprecated for the given reason. +static void markDeprecated(Decl *decl, llvm::Twine message) { + ASTContext &ctx = decl->getASTContext(); + decl->getAttrs().add( + AvailableAttr::createUniversallyDeprecated( + ctx, ctx.AllocateCopy(message.str()))); +} + +/// Find an explicitly-provided "destroy" operation specified for the +/// given Clang type and return it. +FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy( + NominalTypeDecl *nominal, const clang::RecordDecl *clangType) { + if (!clangType->hasAttrs()) + return nullptr; + + llvm::SmallPtrSet matchingDestroyFuncs; + llvm::TinyPtrVector nonMatchingDestroyFuncs; + for (auto attr : clangType->getAttrs()) { + auto swiftAttr = dyn_cast(attr); + if (!swiftAttr) + continue; + + auto destroyFuncName = swiftAttr->getAttribute(); + if (!destroyFuncName.consume_front("destroy:")) + continue; + + auto decls = getValueDeclsForName( + clangType, nominal->getASTContext(), destroyFuncName); + for (auto decl : decls) { + auto func = dyn_cast(decl); + if (!func) + continue; + + auto params = func->getParameters(); + if (params->size() != 1) { + nonMatchingDestroyFuncs.push_back(func); + continue; + } + + if (!params->get(0)->getInterfaceType()->isEqual( + nominal->getDeclaredInterfaceType())) { + nonMatchingDestroyFuncs.push_back(func); + continue; + } + + matchingDestroyFuncs.insert(func); + } + } + + switch (matchingDestroyFuncs.size()) { + case 0: + if (!nonMatchingDestroyFuncs.empty()) { + markDeprecated( + nominal, + "destroy function '" + + nonMatchingDestroyFuncs.front()->getName().getBaseName() + .userFacingName() + + "' must have a single parameter with type '" + + nominal->getDeclaredInterfaceType().getString() + "'"); + } + + return nullptr; + + case 1: + // Handled below. + break; + + default: { + auto iter = matchingDestroyFuncs.begin(); + auto first = *iter++; + auto second = *iter; + markDeprecated( + nominal, + "multiple destroy operations ('" + + first->getName().getBaseName().userFacingName() + + "' and '" + + second->getName().getBaseName().userFacingName() + + "') provided for type"); + return nullptr; + } + } + + auto destroyFunc = *matchingDestroyFuncs.begin(); + + // 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: + case CxxRecordSemanticsKind::Reference: + if (auto cxxRecord = dyn_cast(clangType)) { + if (!cxxRecord->hasTrivialDestructor()) { + markDeprecated( + nominal, + "destroy operation '" + + destroyFunc->getName().getBaseName().userFacingName() + + "' is not allowed on types with a non-trivial destructor"); + return nullptr; + } + } + + LLVM_FALLTHROUGH; + + case CxxRecordSemanticsKind::Trivial: + markDeprecated( + nominal, + "destroy operation '" + + destroyFunc->getName().getBaseName().userFacingName() + + "' is only allowed on non-copyable types; " + "did you mean to use SWIFT_NONCOPYABLE?"); + 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 +/// passes "self" to the given "destroy" function. +static std::pair +synthesizeDeinitBodyForCustomDestroy( + AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) { + auto deinit = cast(deinitFunc); + auto destroyFunc = static_cast(opaqueDestroyFunc); + + ASTContext &ctx = deinit->getASTContext(); + auto funcRef = new (ctx) DeclRefExpr( + destroyFunc, DeclNameLoc(), /*Implicit=*/true); + auto selfRef = new (ctx) DeclRefExpr( + deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true); + auto callExpr = CallExpr::createImplicit( + ctx, funcRef, + ArgumentList::createImplicit( + ctx, + { Argument(SourceLoc(), Identifier(), selfRef)} + ) + ); + + auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) }); + return std::make_pair(braceStmt, /*typechecked=*/false); +} + +void SwiftDeclSynthesizer::addExplicitDeinitIfRequired( + NominalTypeDecl *nominal, const clang::RecordDecl *clangType) { + auto destroyFunc = findExplicitDestroy(nominal, clangType); + if (!destroyFunc) + return; + + ASTContext &ctx = nominal->getASTContext(); + auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal); + destructor->setSynthesized(true); + destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true); + destructor->setBodySynthesizer( + synthesizeDeinitBodyForCustomDestroy, destroyFunc); + + nominal->addMember(destructor); +} + FuncDecl *SwiftDeclSynthesizer::makeAvailabilityDomainPredicate( const clang::VarDecl *var) { ASTContext &ctx = ImporterImpl.SwiftContext; diff --git a/lib/ClangImporter/SwiftDeclSynthesizer.h b/lib/ClangImporter/SwiftDeclSynthesizer.h index 8ee1a105b0a82..974e2d5a93be3 100644 --- a/lib/ClangImporter/SwiftDeclSynthesizer.h +++ b/lib/ClangImporter/SwiftDeclSynthesizer.h @@ -341,6 +341,12 @@ class SwiftDeclSynthesizer { synthesizeStaticFactoryForCXXForeignRef( const clang::CXXRecordDecl *cxxRecordDecl); + /// Look for an explicitly-provided "destroy" operation. If one exists + /// and the type has been imported as noncopyable, add an explicit `deinit` + /// that calls that destroy operation. + void addExplicitDeinitIfRequired( + NominalTypeDecl *nominal, const clang::RecordDecl *clangType); + /// Synthesize a Swift function that calls the Clang runtime predicate /// function for the availability domain represented by `var`. FuncDecl *makeAvailabilityDomainPredicate(const clang::VarDecl *var); @@ -349,6 +355,12 @@ class SwiftDeclSynthesizer { private: Type getConstantLiteralType(Type type, ConstantConvertKind convertKind); + + /// Find an explicitly-provided "destroy" operation specified for the + /// given Clang type and return it. + FuncDecl *findExplicitDestroy( + NominalTypeDecl *nominal, const clang::RecordDecl *clangType); + }; } // namespace swift diff --git a/test/Interop/C/struct/Inputs/noncopyable-struct.h b/test/Interop/C/struct/Inputs/noncopyable-struct.h index b678c3bd702c8..432cacdf3937e 100644 --- a/test/Interop/C/struct/Inputs/noncopyable-struct.h +++ b/test/Interop/C/struct/Inputs/noncopyable-struct.h @@ -8,12 +8,44 @@ typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_att void *storage; } NonCopyableWithDeinit; +typedef struct __attribute__((swift_attr("destroy:freeCopyableType"))) CopyableType { + void *storage; +} CopyableType; + +typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:freeMultiNonCopyable1"))) __attribute__((swift_attr("destroy:freeMultiNonCopyable2"))) MultiNonCopyableType { + void *storage; +} MultiNonCopyableType; + +typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:badDestroy1"))) BadDestroyNonCopyableType { + void *storage; +} BadDestroyNonCopyableType; + +typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:badDestroy2"))) BadDestroyNonCopyableType2 { + void *storage; +} BadDestroyNonCopyableType2; + #ifdef __cplusplus extern "C" { #endif void freeNonCopyableWithDeinit(NonCopyableWithDeinit ncd); +void freeCopyableType(CopyableType ct); + + +void freeMultiNonCopyable1(MultiNonCopyableType ct); +void freeMultiNonCopyable2(MultiNonCopyableType ct); + +void badDestroy1(void); +void badDestroy2(BadDestroyNonCopyableType2 *ptr); #ifdef __cplusplus + +struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:extraDestroy"))) ExtraDestroy { + void *storage; + + ~ExtraDestroy() { } +}; + +void extraDestroy(ExtraDestroy); } #endif diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index 553187c3c4b59..d5871ae6a374c 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -2,8 +2,8 @@ // Check that we get the expected errors for incorrect uses of noncopyable // imported types with both C and C++ interoperability. -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -cxx-interoperability-mode=default +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -verify-additional-prefix conly- +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -DCPLUSPLUS -verify-additional-prefix cplusplus- -cxx-interoperability-mode=default // Check that we get the expected SIL // RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-SIL %s @@ -43,6 +43,26 @@ func testNCWithDeinit() { #endif } +#if ERRORS +// expected-warning@+1{{destroy operation 'freeCopyableType' is only allowed on non-copyable types; did you mean to use SWIFT_NONCOPYABLE?}} +func copyableType(_: CopyableType) { } + +// expected-warning@+1{{'MultiNonCopyableType' is deprecated: multiple destroy operations ('freeMultiNonCopyable1' and 'freeMultiNonCopyable2') provided for type}} +func multiNonCopyableType(_: borrowing MultiNonCopyableType) { } + +// expected-warning@+1{{'BadDestroyNonCopyableType' is deprecated: destroy function 'badDestroy1' must have a single parameter with type 'BadDestroyNonCopyableType'}} +func bad1(_: borrowing BadDestroyNonCopyableType) { } + +// expected-warning@+1{{'BadDestroyNonCopyableType2' is deprecated: destroy function 'badDestroy2' must have a single parameter with type 'BadDestroyNonCopyableType2'}} +func bad2(_: borrowing BadDestroyNonCopyableType2) { } + +#if CPLUSPLUS +// expected-cplusplus-warning@+1{{'ExtraDestroy' is deprecated: destroy operation 'extraDestroy' is not allowed on types with a non-trivial destructor}} +func extra(_: borrowing ExtraDestroy) { } +#endif + +#endif + // CHECK-SIL: sil shared @$sSo21NonCopyableWithDeinitVfD : $@convention(method) (@owned NonCopyableWithDeinit) -> () { // CHECK-SIL: bb0([[SELF:%[0-9]+]] : $NonCopyableWithDeinit): // CHECK-SIL: [[SELF_ALLOC:%[0-9]+]] = alloc_stack $NonCopyableWithDeinit From 8909f24440915b6c21e85ed737467d4714af240a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 15:51:44 -0700 Subject: [PATCH 5/9] Add SWIFT_NONCOPYABLE_WITH_DESTROY to swift/bridging header This packages up the ~Copyable and "destroy" attributes in a macro. --- .../SwiftBridging/swift/bridging | 33 ++++++++++++++++--- .../C/struct/Inputs/noncopyable-struct.h | 4 +-- .../C/struct/noncopyable_structs.swift | 12 +++---- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/ClangImporter/SwiftBridging/swift/bridging b/lib/ClangImporter/SwiftBridging/swift/bridging index 804673a27c878..9f321056bcbb1 100644 --- a/lib/ClangImporter/SwiftBridging/swift/bridging +++ b/lib/ClangImporter/SwiftBridging/swift/bridging @@ -45,7 +45,7 @@ #define _CXX_INTEROP_CONCAT(...) \ _CXX_INTEROP_CONCAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,) -/// Specifies that a C++ `class` or `struct` is reference-counted using +/// Specifies that a C `class` or `struct` is reference-counted using /// the given `retain` and `release` functions. This annotation lets Swift import /// such a type as reference counted type in Swift, taking advantage of Swift's /// automatic reference counting. @@ -76,7 +76,7 @@ __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:_retain)))) \ __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:_release)))) -/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime +/// Specifies that a `class` or `struct` is a reference type whose lifetime /// is presumed to be immortal, i.e. the reference to such object is presumed to /// always be valid. This annotation lets Swift import such a type as a reference /// type in Swift. @@ -160,11 +160,35 @@ #define SWIFT_UNCHECKED_SENDABLE \ __attribute__((swift_attr("@Sendable"))) -/// Specifies that a specific c++ type such class or struct should be imported -/// as a non-copyable Swift value type. +/// Specifies that a class or struct should be imported as a non-copyable +/// Swift value type. #define SWIFT_NONCOPYABLE \ __attribute__((swift_attr("~Copyable"))) +/// Specifies that a class or struct should be imported as a non-copyable +/// Swift value type that calls the given _destroy function when a value is no +/// longer used. +/// +/// This example shows how to use this macro to let Swift know that +/// a given C struct should have its members freed when it goes out of scope: +/// ```c +/// typedef struct SWIFT_NONCOPYABLE_WITH_DESTROY(mytypeFreeMembers) MyType { +/// void *storage +/// } MyType; +/// +/// void mytypeFreeMembers(MyType toBeDestroyed); +/// MyType mytypeCreate(void); +/// ``` +/// +/// Usage in Swift: +/// ```swift +/// let mt = mytypeCreate() +/// let mt2 = mt // consumes mt +/// // once mt2 is unused, Swift will call mytypeFreeMembers(mt2) +#define SWIFT_NONCOPYABLE_WITH_DESTROY(_destroy) \ + __attribute__((swift_attr("~Copyable"))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(destroy:_destroy)))) + /// Specifies that a specific c++ type such class or struct should be imported /// as a non-escapable Swift value type when the non-escapable language feature /// is enabled. @@ -258,6 +282,7 @@ #define SWIFT_MUTATING #define SWIFT_UNCHECKED_SENDABLE #define SWIFT_NONCOPYABLE +#define SWIDT_NONCOPYABLE_WITH_DESTROY(_destroy) #define SWIFT_NONESCAPABLE #define SWIFT_ESCAPABLE #define SWIFT_ESCAPABLE_IF(...) diff --git a/test/Interop/C/struct/Inputs/noncopyable-struct.h b/test/Interop/C/struct/Inputs/noncopyable-struct.h index 432cacdf3937e..321a86469e6b4 100644 --- a/test/Interop/C/struct/Inputs/noncopyable-struct.h +++ b/test/Interop/C/struct/Inputs/noncopyable-struct.h @@ -1,10 +1,10 @@ -// RUN: %target-typecheck-verify-swift -I %S/Inputs/ +#include "swift/bridging" typedef struct __attribute__((swift_attr("~Copyable"))) NonCopyable { float x, y; } NonCopyable; -typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:freeNonCopyableWithDeinit"))) NonCopyableWithDeinit { +typedef struct SWIFT_NONCOPYABLE_WITH_DESTROY(freeNonCopyableWithDeinit) NonCopyableWithDeinit { void *storage; } NonCopyableWithDeinit; diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index d5871ae6a374c..29bd8051db3a3 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -2,17 +2,17 @@ // Check that we get the expected errors for incorrect uses of noncopyable // imported types with both C and C++ interoperability. -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -verify-additional-prefix conly- -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -DCPLUSPLUS -verify-additional-prefix cplusplus- -cxx-interoperability-mode=default +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -verify -DERRORS -verify-additional-prefix conly- +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -verify -DERRORS -DCPLUSPLUS -verify-additional-prefix cplusplus- -cxx-interoperability-mode=default // Check that we get the expected SIL -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-SIL %s -// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck -check-prefix CHECK-SIL %s +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -o - | %FileCheck -check-prefix CHECK-SIL %s +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -o - -cxx-interoperability-mode=default| %FileCheck -check-prefix CHECK-SIL %s // Check that we get the expected IR -// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-IR %s -// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default | %FileCheck -check-prefix CHECK-IR %s +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -o - | %FileCheck -check-prefix CHECK-IR %s +// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -o - -cxx-interoperability-mode=default | %FileCheck -check-prefix CHECK-IR %s import NoncopyableStructs From 8680de42e957e387fcd7597a7516c7a4827408bb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 15:53:55 -0700 Subject: [PATCH 6/9] Add missing test for newtype'd struct imported as an option set --- .../C/struct/Inputs/StructAsOptionSet.apinotes | 14 ++++++++++++++ .../Interop/C/struct/Inputs/struct-as-option-set.h | 6 ++++++ test/Interop/C/struct/typedef_option_set.swift | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 test/Interop/C/struct/Inputs/StructAsOptionSet.apinotes create mode 100644 test/Interop/C/struct/Inputs/struct-as-option-set.h create mode 100644 test/Interop/C/struct/typedef_option_set.swift diff --git a/test/Interop/C/struct/Inputs/StructAsOptionSet.apinotes b/test/Interop/C/struct/Inputs/StructAsOptionSet.apinotes new file mode 100644 index 0000000000000..bf409b50f9945 --- /dev/null +++ b/test/Interop/C/struct/Inputs/StructAsOptionSet.apinotes @@ -0,0 +1,14 @@ +--- +Name: StructAsOptionSet +Typedefs: +- Name: WGPUBufferUsage + SwiftConformsTo: Swift.OptionSet + SwiftWrapper: struct +Globals: +- Name: WGPUBufferUsage_None + SwiftName: WGPUBufferUsage.none +- Name: WGPUBufferUsage_MapRead + SwiftName: WGPUBufferUsage.mapRead +- Name: WGPUBufferUsage_MapWrite + SwiftName: WGPUBufferUsage.mapWrite + diff --git a/test/Interop/C/struct/Inputs/struct-as-option-set.h b/test/Interop/C/struct/Inputs/struct-as-option-set.h new file mode 100644 index 0000000000000..bb50e36b51b2e --- /dev/null +++ b/test/Interop/C/struct/Inputs/struct-as-option-set.h @@ -0,0 +1,6 @@ +#include + +typedef uint32_t WGPUBufferUsage; +static const WGPUBufferUsage WGPUBufferUsage_None = 0x0000000000000000; +static const WGPUBufferUsage WGPUBufferUsage_MapRead = 0x0000000000000001; +static const WGPUBufferUsage WGPUBufferUsage_MapWrite = 0x0000000000000002; diff --git a/test/Interop/C/struct/typedef_option_set.swift b/test/Interop/C/struct/typedef_option_set.swift new file mode 100644 index 0000000000000..12f2b3fdc1361 --- /dev/null +++ b/test/Interop/C/struct/typedef_option_set.swift @@ -0,0 +1,10 @@ +// RUN: %target-typecheck-verify-swift -I %S/Inputs/ + +import StructAsOptionSet + +func takeOptionSet(_: T) { } + +func useStructAsOptionSet() { + let usage: WGPUBufferUsage = [.mapRead, .mapWrite] + takeOptionSet(usage) +} From 34545e0df8a4be8b832223db865b952e7b79bf7f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 28 Jul 2025 19:10:21 -0700 Subject: [PATCH 7/9] Disable part of this test on Windows --- test/Interop/C/struct/noncopyable_structs.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index 29bd8051db3a3..f5ffa785c82f9 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -56,10 +56,12 @@ func bad1(_: borrowing BadDestroyNonCopyableType) { } // expected-warning@+1{{'BadDestroyNonCopyableType2' is deprecated: destroy function 'badDestroy2' must have a single parameter with type 'BadDestroyNonCopyableType2'}} func bad2(_: borrowing BadDestroyNonCopyableType2) { } +#if !os(Windows) #if CPLUSPLUS // expected-cplusplus-warning@+1{{'ExtraDestroy' is deprecated: destroy operation 'extraDestroy' is not allowed on types with a non-trivial destructor}} func extra(_: borrowing ExtraDestroy) { } #endif +#endif #endif From 71b96e9db9458bb7dd044a955077237e05e3e9c4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 29 Jul 2025 08:05:28 -0700 Subject: [PATCH 8/9] Split out the part of the test that's failing on Windows --- test/Interop/C/struct/noncopyable_structs.swift | 8 -------- .../C/struct/noncopyable_structs_nontrivial.swift | 12 ++++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 test/Interop/C/struct/noncopyable_structs_nontrivial.swift diff --git a/test/Interop/C/struct/noncopyable_structs.swift b/test/Interop/C/struct/noncopyable_structs.swift index f5ffa785c82f9..082632ffffac6 100644 --- a/test/Interop/C/struct/noncopyable_structs.swift +++ b/test/Interop/C/struct/noncopyable_structs.swift @@ -1,4 +1,3 @@ - // Check that we get the expected errors for incorrect uses of noncopyable // imported types with both C and C++ interoperability. @@ -56,13 +55,6 @@ func bad1(_: borrowing BadDestroyNonCopyableType) { } // expected-warning@+1{{'BadDestroyNonCopyableType2' is deprecated: destroy function 'badDestroy2' must have a single parameter with type 'BadDestroyNonCopyableType2'}} func bad2(_: borrowing BadDestroyNonCopyableType2) { } -#if !os(Windows) -#if CPLUSPLUS -// expected-cplusplus-warning@+1{{'ExtraDestroy' is deprecated: destroy operation 'extraDestroy' is not allowed on types with a non-trivial destructor}} -func extra(_: borrowing ExtraDestroy) { } -#endif -#endif - #endif // CHECK-SIL: sil shared @$sSo21NonCopyableWithDeinitVfD : $@convention(method) (@owned NonCopyableWithDeinit) -> () { diff --git a/test/Interop/C/struct/noncopyable_structs_nontrivial.swift b/test/Interop/C/struct/noncopyable_structs_nontrivial.swift new file mode 100644 index 0000000000000..2d19a079b8d9a --- /dev/null +++ b/test/Interop/C/struct/noncopyable_structs_nontrivial.swift @@ -0,0 +1,12 @@ +// Split out from noncopyable_structs.swift due to an issue with Windows. + +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -verify -DERRORS -verify-additional-prefix conly- +// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ -I %swift_src_root/lib/ClangImporter/SwiftBridging %s -verify -DERRORS -DCPLUSPLUS -verify-additional-prefix cplusplus- -cxx-interoperability-mode=default + +// XFAIL: OS=windows-msvc +import NoncopyableStructs + +#if CPLUSPLUS +// expected-cplusplus-warning@+1{{'ExtraDestroy' is deprecated: destroy operation 'extraDestroy' is not allowed on types with a non-trivial destructor}} +func extra(_: borrowing ExtraDestroy) { } +#endif From 2e7e8843cac1544ec226230d706ea9d5b66013c6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 29 Jul 2025 08:09:56 -0700 Subject: [PATCH 9/9] Documentation comment cleanups --- .../SwiftBridging/swift/bridging | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/ClangImporter/SwiftBridging/swift/bridging b/lib/ClangImporter/SwiftBridging/swift/bridging index 9f321056bcbb1..4c8e97d4a8e80 100644 --- a/lib/ClangImporter/SwiftBridging/swift/bridging +++ b/lib/ClangImporter/SwiftBridging/swift/bridging @@ -1,5 +1,5 @@ -// -*- C++ -*- -//===------------------ bridging - C++ and Swift Interop --------*- C++ -*-===// +// -*- C -*- +//===------------------ bridging - C and Swift Interop ----------*- C++ -*-===// // // This source file is part of the Swift.org open source project // @@ -32,7 +32,7 @@ /// that return a `class` or `struct` type that is annotated with this macro. #define SWIFT_SELF_CONTAINED __attribute__((swift_attr("import_owned"))) -/// Specifies that a C++ method returns a value that is presumed to contain +/// Specifies that a method returns a value that is presumed to contain /// objects whose lifetime is not dependent on `this` or other parameters passed /// to the method. #define SWIFT_RETURNS_INDEPENDENT_VALUE __attribute__((swift_attr("import_unsafe"))) @@ -45,7 +45,7 @@ #define _CXX_INTEROP_CONCAT(...) \ _CXX_INTEROP_CONCAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,) -/// Specifies that a C `class` or `struct` is reference-counted using +/// Specifies that a `class` or `struct` is reference-counted using /// the given `retain` and `release` functions. This annotation lets Swift import /// such a type as reference counted type in Swift, taking advantage of Swift's /// automatic reference counting. @@ -103,7 +103,7 @@ __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:immortal)))) \ __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:immortal)))) -/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime +/// Specifies that a `class` or `struct` is a reference type whose lifetime /// is not managed automatically. The programmer must validate that any reference /// to such object is valid themselves. This annotation lets Swift import such a type as a reference type in Swift. #define SWIFT_UNSAFE_REFERENCE \ @@ -115,7 +115,7 @@ /// Specifies a name that will be used in Swift for this declaration instead of its original name. #define SWIFT_NAME(_name) __attribute__((swift_name(#_name))) -/// Specifies that a specific C++ `class` or `struct` conforms to a +/// Specifies that a specific `class` or `struct` conforms to a /// a specific Swift protocol. /// /// This example shows how to use this macro to conform a class template to a Swift protocol: @@ -147,7 +147,7 @@ #define SWIFT_MUTATING \ __attribute__((swift_attr("mutating"))) -/// Specifies that a specific c++ type such class or struct should be imported as type marked +/// Specifies that a specific class or struct should be imported as type marked /// as `@unchecked Sendable` type in swift. If this annotation is used, the type is therefore allowed to /// use safely across async contexts. /// @@ -160,13 +160,13 @@ #define SWIFT_UNCHECKED_SENDABLE \ __attribute__((swift_attr("@Sendable"))) -/// Specifies that a class or struct should be imported as a non-copyable +/// Specifies that a `class` or `struct` should be imported as a non-copyable /// Swift value type. #define SWIFT_NONCOPYABLE \ __attribute__((swift_attr("~Copyable"))) -/// Specifies that a class or struct should be imported as a non-copyable -/// Swift value type that calls the given _destroy function when a value is no +/// Specifies that a `class` or `struct` should be imported as a non-copyable +/// Swift value type that calls the given `_destroy` function when a value is no /// longer used. /// /// This example shows how to use this macro to let Swift know that @@ -185,18 +185,18 @@ /// let mt = mytypeCreate() /// let mt2 = mt // consumes mt /// // once mt2 is unused, Swift will call mytypeFreeMembers(mt2) +/// ``` #define SWIFT_NONCOPYABLE_WITH_DESTROY(_destroy) \ __attribute__((swift_attr("~Copyable"))) \ __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(destroy:_destroy)))) -/// Specifies that a specific c++ type such class or struct should be imported -/// as a non-escapable Swift value type when the non-escapable language feature -/// is enabled. +/// Specifies that a specific class or struct should be imported +/// as a non-escapable Swift value type. #define SWIFT_NONESCAPABLE \ __attribute__((swift_attr("~Escapable"))) -/// Specifies that a specific c++ type such class or struct should be imported -/// as a escapable Swift value. While this matches the default behavior, +/// Specifies that a specific class or struct should be imported +/// as an escapable Swift value. While this matches the default behavior, /// in safe mode interop mode it ensures that the type is not marked as /// unsafe. #define SWIFT_ESCAPABLE \ @@ -207,16 +207,16 @@ #define SWIFT_ESCAPABLE_IF(...) \ __attribute__((swift_attr("escapable_if:" _CXX_INTEROP_CONCAT(__VA_ARGS__)))) -/// Specifies that the return value is passed as owned for C++ functions and +/// Specifies that the return value is passed as owned for functions and /// methods returning types annotated as `SWIFT_SHARED_REFERENCE` #define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained"))) -/// Specifies that the return value is passed as unowned for C++ functions and +/// Specifies that the return value is passed as unowned for functions and /// methods returning types annotated as `SWIFT_SHARED_REFERENCE` #define SWIFT_RETURNS_UNRETAINED \ __attribute__((swift_attr("returns_unretained"))) -/// Applied to a C++ foreign reference type annotated with -/// SWIFT_SHARED_REFERENCE. Indicates that C++ APIs returning this type are +/// Applied to a foreign reference type annotated with +/// SWIFT_SHARED_REFERENCE. Indicates that APIs returning this type are /// assumed to return an unowned (+0) value by default, unless explicitly annotated /// with SWIFT_RETURNS_RETAINED. /// @@ -227,7 +227,7 @@ /// Bar { ... }; /// ``` /// -/// In Swift, C++ APIs returning `Bar*` will be assumed to return an unowned +/// In Swift, APIs returning `Bar*` will be assumed to return an unowned /// value. #define SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT \ __attribute__((swift_attr("returned_as_unretained_by_default")))