Skip to content

🍒 [cxx-interop] Add support for SWIFT_RETURNS_(UN)RETAINED for ObjC APIs returning C++ FRT #78484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 23 additions & 29 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,25 @@ class Conventions {
}
llvm_unreachable("unhandled ownership");
}

// Determines owned/unowned ResultConvention of the returned value based on
// returns_retained/returns_unretained attribute.
std::optional<ResultConvention>
getCxxRefConventionWithAttrs(const TypeLowering &tl,
const clang::Decl *decl) const {
if (tl.getLoweredType().isForeignReferenceType() && decl->hasAttrs()) {
for (const auto *attr : decl->getAttrs()) {
if (const auto *swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) {
if (swiftAttr->getAttribute() == "returns_unretained") {
return ResultConvention::Unowned;
} else if (swiftAttr->getAttribute() == "returns_retained") {
return ResultConvention::Owned;
}
}
}
}
return std::nullopt;
}
};

/// A visitor for breaking down formal result types into a SILResultInfo
Expand Down Expand Up @@ -3335,7 +3354,8 @@ class ObjCMethodConventions : public Conventions {
return ResultConvention::Owned;

if (tl.getLoweredType().isForeignReferenceType())
return ResultConvention::Unowned;
return getCxxRefConventionWithAttrs(tl, Method)
.value_or(ResultConvention::Unowned);

return ResultConvention::Autoreleased;
}
Expand Down Expand Up @@ -3375,25 +3395,6 @@ class CFunctionTypeConventions : public Conventions {
const clang::FunctionType *type)
: Conventions(kind), FnType(type) {}

// Determines owned/unowned ResultConvention of the returned value based on
// returns_retained/returns_unretained attribute.
std::optional<ResultConvention>
getForeignReferenceTypeResultConventionWithAttributes(
const TypeLowering &tl, const clang::FunctionDecl *decl) const {
if (tl.getLoweredType().isForeignReferenceType() && decl->hasAttrs()) {
for (const auto *attr : decl->getAttrs()) {
if (const auto *swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) {
if (swiftAttr->getAttribute() == "returns_unretained") {
return ResultConvention::Unowned;
} else if (swiftAttr->getAttribute() == "returns_retained") {
return ResultConvention::Owned;
}
}
}
}
return std::nullopt;
}

public:
CFunctionTypeConventions(const clang::FunctionType *type)
: Conventions(ConventionsKind::CFunctionType), FnType(type) {}
Expand Down Expand Up @@ -3511,11 +3512,7 @@ class CFunctionConventions : public CFunctionTypeConventions {
return ResultConvention::Indirect;
}

// Explicitly setting the ownership of the returned FRT if the C++
// global/free function has either swift_attr("returns_retained") or
// ("returns_unretained") attribute.
if (auto resultConventionOpt =
getForeignReferenceTypeResultConventionWithAttributes(tl, TheDecl))
if (auto resultConventionOpt = getCxxRefConventionWithAttrs(tl, TheDecl))
return *resultConventionOpt;

if (isCFTypedef(tl, TheDecl->getReturnType())) {
Expand Down Expand Up @@ -3597,11 +3594,8 @@ class CXXMethodConventions : public CFunctionTypeConventions {
return ResultConvention::Indirect;
}

// Explicitly setting the ownership of the returned FRT if the C++ member
// method has either swift_attr("returns_retained") or
// ("returns_unretained") attribute.
if (auto resultConventionOpt =
getForeignReferenceTypeResultConventionWithAttributes(resultTL, TheDecl))
getCxxRefConventionWithAttrs(resultTL, TheDecl))
return *resultConventionOpt;

if (TheDecl->hasAttr<clang::CFReturnsRetainedAttr>() &&
Expand Down
38 changes: 38 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/cxx-frt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <Foundation/Foundation.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-extension"
#pragma clang assume_nonnull begin

struct CxxRefType {
} __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:retainCxxRefType")))
__attribute__((swift_attr("release:releaseCxxRefType")));

void retainCxxRefType(CxxRefType *_Nonnull b) {}
void releaseCxxRefType(CxxRefType *_Nonnull b) {}

@interface Bridge : NSObject

+ (struct CxxRefType *)objCMethodReturningFRTUnannotated;
+ (struct CxxRefType *)objCMethodReturningFRTUnowned
__attribute__((swift_attr("returns_unretained")));
+ (struct CxxRefType *)objCMethodReturningFRTOwned
__attribute__((swift_attr("returns_retained")));

@end

@implementation Bridge
+ (struct CxxRefType *)objCMethodReturningFRTUnannotated {
};
+ (struct CxxRefType *)objCMethodReturningFRTUnowned
__attribute__((swift_attr("returns_unretained"))) {
}
+ (struct CxxRefType *)objCMethodReturningFRTOwned
__attribute__((swift_attr("returns_retained"))) {
}

@end

#pragma clang diagnostic pop
#pragma clang assume_nonnull end
5 changes: 5 additions & 0 deletions test/Interop/Cxx/objc-correctness/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ module ID {
requires objc
requires cplusplus
}

module CxxForeignRef {
header "cxx-frt.h"
requires cplusplus
}
16 changes: 16 additions & 0 deletions test/Interop/Cxx/objc-correctness/objc-returning-cxx-frt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// RUN: %target-swift-emit-sil -I %S/Inputs -cxx-interoperability-mode=upcoming-swift -disable-availability-checking -diagnostic-style llvm %s -validate-tbd-against-ir=none -Xcc -fignore-exceptions | %FileCheck %s

import CxxForeignRef

// REQUIRES: objc_interop

func testObjCMethods() {
var frt1 = Bridge.objCMethodReturningFRTUnannotated()
// CHECK: objc_method {{.*}} #Bridge.objCMethodReturningFRTUnannotated!foreign : (Bridge.Type) -> () -> CxxRefType, $@convention(objc_method) (@objc_metatype Bridge.Type) -> CxxRefType

var frt2 = Bridge.objCMethodReturningFRTUnowned()
// CHECK: objc_method {{.*}} #Bridge.objCMethodReturningFRTUnowned!foreign : (Bridge.Type) -> () -> CxxRefType, $@convention(objc_method) (@objc_metatype Bridge.Type) -> CxxRefType

var frt3 = Bridge.objCMethodReturningFRTOwned()
// CHECK: objc_method {{.*}} #Bridge.objCMethodReturningFRTOwned!foreign : (Bridge.Type) -> () -> CxxRefType, $@convention(objc_method) (@objc_metatype Bridge.Type) -> @owned CxxRefType
}