From edcde7c55c719a50a46ca129c1d5ce9f2d5d1b6e Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Wed, 7 May 2025 17:59:51 +0100 Subject: [PATCH] [ClangImporter] Import 'swift_attr("sending")' As a Type Attribute Previously, `__attribute__((swift_attr("sending")))` would only be resolved when attached to declarations. This patch expands it to be a type attribute as well, which enables it to occur in the result and parameter positions for blocks, function pointers, and lambdas. Resolves rdar://148435359 --- lib/ClangImporter/ImportType.cpp | 39 ++++++++++++++++++++++++++--- lib/ClangImporter/ImporterImpl.h | 4 +++ test/ClangImporter/Inputs/sending.h | 3 +++ test/ClangImporter/sending.swift | 17 ++++++++++++- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 3952c7f2179..fb3ec11de5a 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -726,20 +726,45 @@ namespace { paramQualType->getPointeeType().isConstQualified()) paramQualType = paramQualType->getPointeeType(); + // Mark any `sending` parameters if need be. + ImportTypeAttrs paramAttributes; + if (Impl.SwiftContext.LangOpts.hasFeature(Feature::SendingArgsAndResults)) { + getConcurrencyAttrs(Impl.SwiftContext, ImportTypeKind::Parameter, + paramAttributes, paramQualType); + } + auto swiftParamTy = Impl.importTypeIgnoreIUO( paramQualType, paramImportKind, addImportDiagnostic, AllowNSUIntegerAsInt, Bridging, ImportTypeAttrs(), OTK_Optional); if (!swiftParamTy) return Type(); + ParameterTypeFlags flags; + flags = flags.withSending( + paramAttributes.contains(ImportTypeAttr::Sending)); + // FIXME(https://github.com/apple/swift/issues/45134): If we were walking TypeLocs, we could actually get parameter names. // The probably doesn't matter outside of a FuncDecl, which we'll have // to special-case, but it's an interesting bit of data loss. - params.push_back(FunctionType::Param(swiftParamTy)); + params.emplace_back(swiftParamTy, Identifier(), flags); + } + + // Mark any `sending` result types if need be. + auto extInfo = FunctionType::ExtInfo(); + ImportTypeAttrs resultAttributes; + if (Impl.SwiftContext.LangOpts.hasFeature(Feature::SendingArgsAndResults)) { + getConcurrencyAttrs(Impl.SwiftContext, ImportTypeKind::Result, + resultAttributes, type->getReturnType()); + + const bool sending = resultAttributes.contains(ImportTypeAttr::Sending); + extInfo = FunctionType::ExtInfo() + .intoBuilder() + .withSendingResult(sending) + .build(); } // Form the function type. - return FunctionType::get(params, resultTy, FunctionType::ExtInfo()); + return FunctionType::get(params, resultTy, extInfo); } ImportResult @@ -1714,6 +1739,7 @@ void swift::getConcurrencyAttrs(ASTContext &SwiftContext, SwiftContext.LangOpts.hasFeature(Feature::SendableCompletionHandlers) && importKind == ImportTypeKind::CompletionHandlerParameter; bool isNonSendable = false; + bool isSending = false; // Consider only immediate attributes, don't look through the typerefs // because they are imported separately. @@ -1721,10 +1747,13 @@ void swift::getConcurrencyAttrs(ASTContext &SwiftContext, if (isMainActorAttr(attr)) { isMainActor = true; isSendable = true; // MainActor implies Sendable - } else if (attr->getAttribute() == "@Sendable") + } else if (attr->getAttribute() == "@Sendable") { isSendable = true; - else if (attr->getAttribute() == "@_nonSendable") + } else if (attr->getAttribute() == "@_nonSendable") { isNonSendable = true; + } else if (attr->getAttribute() == "sending") { + isSending = true; + } }); if (isMainActor) @@ -1733,6 +1762,8 @@ void swift::getConcurrencyAttrs(ASTContext &SwiftContext, attrs |= ImportTypeAttr::Sendable; if (isNonSendable) attrs -= ImportTypeAttr::Sendable; + if (isSending) + attrs |= ImportTypeAttr::Sending; } ImportedType ClangImporter::Implementation::importType( diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 29f95de7f20..db9baab4dd0 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -217,6 +217,10 @@ enum class ImportTypeAttr : uint8_t { /// /// This ensures that the parameter is not marked as Unmanaged. CFUnretainedOutParameter = 1 << 5, + + /// Type should be imported as though declaration was marked with + /// \c __attribute__((swift_attr("sending"))) . + Sending = 1 << 6, }; /// Find and iterate over swift attributes embedded in the type diff --git a/test/ClangImporter/Inputs/sending.h b/test/ClangImporter/Inputs/sending.h index 04f037caf41..a3b8602d5c8 100644 --- a/test/ClangImporter/Inputs/sending.h +++ b/test/ClangImporter/Inputs/sending.h @@ -43,4 +43,7 @@ sendUserDefinedFromGlobalFunction(NonSendableCStruct other) SWIFT_SENDING; void sendUserDefinedIntoGlobalFunction( NonSendableCStruct arg SWIFT_SENDING); +void sendingWithCompletionHandler(void (^completion)(SWIFT_SENDING NonSendableCStruct arg)); +SWIFT_SENDING NonSendableCStruct sendingWithLazyReturn(SWIFT_SENDING NonSendableCStruct (^makeLazily)(void)); + #pragma clang assume_nonnull end diff --git a/test/ClangImporter/sending.swift b/test/ClangImporter/sending.swift index a8bc236d9e6..188ff210c9f 100644 --- a/test/ClangImporter/sending.swift +++ b/test/ClangImporter/sending.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -swift-version 6 -disable-availability-checking -emit-sil -o /dev/null %s -parse-as-library -enable-experimental-feature SendingArgsAndResults -verify -import-objc-header %S/Inputs/sending.h +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -swift-version 6 -disable-availability-checking -emit-sil -o /dev/null %s -parse-as-library -enable-experimental-feature SendingArgsAndResults -verify -import-objc-header %S/Inputs/sending.h // REQUIRES: concurrency // REQUIRES: swift_feature_SendingArgsAndResults @@ -38,3 +38,18 @@ func funcTestSendingArg() async { // expected-note @-1 {{'x' used after being passed as a 'sending' parameter}} useValue(x) // expected-note {{access can happen concurrently}} } + +func funcTestSendingClosureArg() async { + sendingWithCompletionHandler { (x: sending NonSendableCStruct) in + sendUserDefinedIntoGlobalFunction(x) // expected-error {{sending 'x' risks causing data races}} + // expected-note @-1 {{'x' used after being passed as a 'sending' parameter}} + useValue(x) // expected-note {{access can happen concurrently}} + } + + let x = sendingWithLazyReturn { () -> sending NonSendableCStruct in + NonSendableCStruct() + } + sendUserDefinedIntoGlobalFunction(x) // expected-error {{sending 'x' risks causing data races}} + // expected-note @-1 {{'x' used after being passed as a 'sending' parameter}} + useValue(x) // expected-note {{access can happen concurrently}} +}