Skip to content

[cxx-interop] Introduce type-level annotations to specify default ownership convention for C++ foreign reference return values #81093

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
57 changes: 57 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3599,6 +3599,50 @@ namespace {
std::nullopt);
}

template <typename T>
std::optional<T>
matchSwiftAttr(const clang::Decl *decl,
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
if (!decl || !decl->hasAttrs())
return std::nullopt;

for (const auto *attr : decl->getAttrs()) {
if (const auto *swiftAttr =
llvm::dyn_cast<clang::SwiftAttrAttr>(attr)) {
for (const auto &p : patterns) {
if (swiftAttr->getAttribute() == p.first)
return p.second;
}
}
}
return std::nullopt;
}

template <typename T>
std::optional<T> matchSwiftAttrConsideringInheritance(
const clang::Decl *decl,
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
if (!decl)
return std::nullopt;

if (auto match = matchSwiftAttr<T>(decl, patterns))
return match;

if (const auto *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
for (const auto &baseSpecifier : recordDecl->bases()) {
const clang::CXXRecordDecl *baseDecl =
baseSpecifier.getType()->getAsCXXRecordDecl();
if (!baseDecl)
continue;
if (auto match =
matchSwiftAttrConsideringInheritance<T>(baseDecl, patterns))
return match;
}
}

return std::nullopt;
}

/// Emit diagnostics for incorrect usage of SWIFT_RETURNS_RETAINED and
/// SWIFT_RETURNS_UNRETAINED
void checkBridgingAttrs(const clang::NamedDecl *decl) {
Expand Down Expand Up @@ -3664,6 +3708,19 @@ namespace {
unannotatedAPIWarningNeeded = true;
}

if (const auto *returnPtrTy = retType->getAs<clang::PointerType>()) {
if (clang::RecordDecl *returnRecordDecl =
returnPtrTy->getPointeeType()->getAsRecordDecl()) {
if (auto match = matchSwiftAttrConsideringInheritance<bool>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: match is unused here, so you don't need if (auto match = condition); just if (condition) should suffice.

returnRecordDecl,
{
{"returns_retained_by_default", true},
{"returns_unretained_by_default", true},
}))
unannotatedAPIWarningNeeded = false;
}
}

if (unannotatedAPIWarningNeeded) {
HeaderLoc loc(decl->getLocation());
Impl.diagnose(loc, diag::no_returns_retained_returns_unretained,
Expand Down
14 changes: 14 additions & 0 deletions lib/ClangImporter/SwiftBridging/swift/bridging
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@
#define SWIFT_RETURNS_UNRETAINED \
__attribute__((swift_attr("returns_unretained")))

/// Specifies that a C++ API returning this C++ foreign reference type is
/// assumed to return an owned (+1) reference by default, unless explicitly
/// annotated with SWIFT_RETURNS_UNRETAINED. This annotation is only valid on
/// types that are already annotated with SWIFT_SHARED_REFERENCE.
#define SWIFT_RETURNS_RETAINED_BY_DEFAULT \
Comment on lines +194 to +198
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to bike-shed here, but I find the name a bit confusing because SWIFT_RETURNS_RETAINED_BY_DEFAULT is reminiscent of SWIFT_RETURNS_RETAINED, but are used to annotate different language constructs.

I think at the very least, we can update the documentation to first say "this foreign reference type," so that it's clear upfront what is being annotated:

Suggested change
/// Specifies that a C++ API returning this C++ foreign reference type is
/// assumed to return an owned (+1) reference by default, unless explicitly
/// annotated with SWIFT_RETURNS_UNRETAINED. This annotation is only valid on
/// types that are already annotated with SWIFT_SHARED_REFERENCE.
#define SWIFT_RETURNS_RETAINED_BY_DEFAULT \
/// Specifies that this foreign reference type is conventionally returned
/// as an owned (+1) reference. In other words, C++ APIs that return this
/// type which are not explicitly annotated as SWIFT_RETURNS_UNRETAINED
/// are assumed to be implicitly annotated as SWIFT_RETURNS_RETAINED. The
/// SWIFT_RETURNS_RETAINED_BY_DEFAULT annotation is only valid on types
/// that are already annotated with SWIFT_SHARED_REFERENCE.
#define SWIFT_RETURNS_RETAINED_BY_DEFAULT \

An example could be helpful too, but I'm not sure what our appetite for verbosity should be here.

🚲🏠⏬

Returning to my point about why I find the name of this annotation a bit confusing: when we annotate a function f with, say, SWIFT_RETURNS_RETAINED, we are saying "f returns a retained FRT." In other words, the subject of "returns" is f, the function we annotated.

But in this case, when we annotate an FRT T with SWIFT_RETURNS_RETAINED_BY_DEFAULT, we are not saying "T returns a retained FRT by default" (which doesn't really make much sense). Instead, T is the FRT, so we are really saying "T is returned as retained by default convention" (I think "convention" is the slightly more appropriate word choice, though maybe that word is slightly overloaded).

Thus, my suggestion is to rename this annotation to something like SWIFT_RETURNED_AS_RETAINED_BY_CONVENTION or SWIFT_CONVENTIONALLY_RETURNED_AS_RETAINED. That said, I'm not up to speed on existing naming conventions, so I would like others to weigh in here.

__attribute__((swift_attr("returns_retained_by_default")))

/// Specifies that a C++ API returning this C++ foreign reference type is
/// assumed to return an unowned (+0) reference by default, unless explicitly
/// annotated with SWIFT_RETURNS_RETAINED. This annotation is only valid on
/// types that are already annotated with SWIFT_SHARED_REFERENCE.
#define SWIFT_RETURNS_UNRETAINED_BY_DEFAULT \
__attribute__((swift_attr("returns_unretained_by_default")))

/// Specifies that the non-public members of a C++ class, struct, or union can
/// be accessed from extensions of that type, in the given file ID.
///
Expand Down
86 changes: 74 additions & 12 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,49 @@ enum class ConventionsKind : uint8_t {
CXXMethod = 8,
};

template <typename T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper function should be put into a header file, so that it can be used in both lib/SIL/IR/SILFunctionType.cpp and lib/ClangImporter/ImportDecl.cpp. (Perhaps it can go in ClangImporter.h, in the importer namespace?)

std::optional<T>
matchSwiftAttr(const clang::Decl *decl,
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
if (!decl || !decl->hasAttrs())
return std::nullopt;

for (const auto *attr : decl->getAttrs()) {
if (const auto *swiftAttr = llvm::dyn_cast<clang::SwiftAttrAttr>(attr)) {
for (const auto &p : patterns) {
if (swiftAttr->getAttribute() == p.first)
return p.second;
}
}
}
return std::nullopt;
}

template <typename T>
std::optional<T> matchSwiftAttrConsideringInheritance(
const clang::Decl *decl,
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
if (!decl)
return std::nullopt;

if (auto match = matchSwiftAttr<T>(decl, patterns))
return match;

if (const auto *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
for (const auto &baseSpecifier : recordDecl->bases()) {
const clang::CXXRecordDecl *baseDecl =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you should be able to do const auto *baseDecl here, right? It's already clear what the type of baseDecl is from the name of the method that returns it (i.e., getAsCXXRecordDecl()).

baseSpecifier.getType()->getAsCXXRecordDecl();
if (!baseDecl)
continue;
if (auto match =
matchSwiftAttrConsideringInheritance<T>(baseDecl, patterns))
return match;
}
}

return std::nullopt;
}

class Conventions {
ConventionsKind kind;

Expand Down Expand Up @@ -1345,21 +1388,40 @@ class Conventions {
llvm_unreachable("unhandled ownership");
}

// Determines owned/unowned ResultConvention of the returned value based on
// returns_retained/returns_unretained attribute.
// Determines the ownership ResultConvention (owned/unowned) of the return
// value using the SWIFT_RETURNS_(UN)RETAINED annotation on the C++ API; if
// not explicitly annotated, falls back to the
// SWIFT_RETURNS_(UN)RETAINED_BY_DEFAULT annotation on the C++
// SWIFT_SHARED_REFERENCE type.
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;
}
}
}
if (!tl.getLoweredType().isForeignReferenceType())
return std::nullopt;

if (auto result = matchSwiftAttr<ResultConvention>(
decl, {{"returns_unretained", ResultConvention::Unowned},
{"returns_retained", ResultConvention::Owned}}))
return result;

const clang::Type *returnTy = nullptr;
if (const auto *func = llvm::dyn_cast<clang::FunctionDecl>(decl))
returnTy = func->getReturnType().getTypePtrOrNull();
else if (const auto *method = llvm::dyn_cast<clang::ObjCMethodDecl>(decl))
returnTy = method->getReturnType().getTypePtrOrNull();
if (!returnTy)
return std::nullopt;
const clang::Type *desugaredReturnTy =
returnTy->getUnqualifiedDesugaredType();

if (const auto *ptrType =
llvm::dyn_cast<clang::PointerType>(desugaredReturnTy)) {
if (clang::RecordDecl *clangRecordDecl =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (clang::RecordDecl *clangRecordDecl =
if (const clang::RecordDecl *clangRecordDecl =

ptrType->getPointeeType()->getAsRecordDecl())
return matchSwiftAttrConsideringInheritance<ResultConvention>(
clangRecordDecl,
{{"returns_unretained_by_default", ResultConvention::Unowned},
{"returns_retained_by_default", ResultConvention::Owned}});
}
return std::nullopt;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include "visibility.h"
#include <functional>

// FRT or SWIFT_SHARED_REFERENCE type
Expand Down Expand Up @@ -326,3 +327,175 @@ struct Derived : Base {
FRTStruct *_Nonnull VirtualMethodReturningFRTUnowned() override
__attribute__((swift_attr("returns_unretained")));
};

SWIFT_BEGIN_NULLABILITY_ANNOTATIONS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? (Not a rhetorical question, I haven't seen this before and do not know what it is)


namespace DefaultOwnershipConventionOnCXXForegnRefType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
namespace DefaultOwnershipConventionOnCXXForegnRefType {
namespace DefaultOwnershipConventionOnCXXForeignRefType {

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defRetain1")))
__attribute__((swift_attr("release:defRelease1"))) __attribute__((
swift_attr("returns_retained_by_default"))) RefTypeDefaultRetained {};

RefTypeDefaultRetained *returnRefTypeDefaultRetained() {
return new RefTypeDefaultRetained();
}

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defRetain2")))
__attribute__((swift_attr("release:defRelease2"))) __attribute__((
swift_attr("returns_unretained_by_default"))) RefTypeDefaultUnretained {};

RefTypeDefaultUnretained *returnRefTypeDefaultUnretained() {
return new RefTypeDefaultUnretained();
}
} // namespace DefaultOwnershipConventionOnCXXForegnRefType

void defRetain1(
DefaultOwnershipConventionOnCXXForegnRefType::RefTypeDefaultRetained *v) {};
void defRelease1(
DefaultOwnershipConventionOnCXXForegnRefType::RefTypeDefaultRetained *v) {};

void defRetain2(
DefaultOwnershipConventionOnCXXForegnRefType::RefTypeDefaultUnretained *v) {
};
void defRelease2(
DefaultOwnershipConventionOnCXXForegnRefType::RefTypeDefaultUnretained *v) {
};

namespace FunctionAnnotationHasPrecedence {
struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defaultRetain1")))
__attribute__((swift_attr("release:defaultRelease1"))) __attribute__((
swift_attr("returns_unretained_by_default"))) RefTypeDefaultUnRetained {};

RefTypeDefaultUnRetained *returnRefTypeDefaultUnRetained() {
return new RefTypeDefaultUnRetained();
}
RefTypeDefaultUnRetained *returnRefTypeDefaultUnRetainedAnnotatedRetained()
__attribute__((swift_attr("returns_retained"))) {
return new RefTypeDefaultUnRetained();
}

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defaultRetain2")))
__attribute__((swift_attr("release:defaultRelease2"))) __attribute__((
swift_attr("returns_retained_by_default"))) RefTypeDefaultRetained {};

RefTypeDefaultRetained *returnRefTypeDefaultRetained() {
return new RefTypeDefaultRetained();
}
RefTypeDefaultRetained *returnRefTypeDefaultRetainedAnnotatedUnRetained()
__attribute__((swift_attr("returns_unretained"))) {
return new RefTypeDefaultRetained();
}

} // namespace FunctionAnnotationHasPrecedence

void defaultRetain1(
FunctionAnnotationHasPrecedence::RefTypeDefaultUnRetained *v) {};
void defaultRelease1(
FunctionAnnotationHasPrecedence::RefTypeDefaultUnRetained *v) {};

void defaultRetain2(
FunctionAnnotationHasPrecedence::RefTypeDefaultRetained *v) {};
void defaultRelease2(
FunctionAnnotationHasPrecedence::RefTypeDefaultRetained *v) {};

namespace DefaultOwnershipSuppressUnannotatedAPIWarning {

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:rretain")))
__attribute__((swift_attr("release:rrelease"))) RefType {};

RefType *returnRefType() { return new RefType(); } // expected-warning {{'returnRefType' should be annotated with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED as it is returning a SWIFT_SHARED_REFERENCE}}

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:dretain")))
__attribute__((swift_attr("release:drelease"))) __attribute__((
swift_attr("returns_retained_by_default"))) RefTypeDefaultRetained {};

RefTypeDefaultRetained *returnRefTypeDefaultRetained() {
return new RefTypeDefaultRetained();
}

} // namespace DefaultOwnershipSuppressUnannotatedAPIWarning

void rretain(DefaultOwnershipSuppressUnannotatedAPIWarning::RefType *v) {};
void rrelease(DefaultOwnershipSuppressUnannotatedAPIWarning::RefType *v) {};

void dretain(
DefaultOwnershipSuppressUnannotatedAPIWarning::RefTypeDefaultRetained *v) {
};
void drelease(
DefaultOwnershipSuppressUnannotatedAPIWarning::RefTypeDefaultRetained *v) {
};

namespace DefaultOwnershipInheritance {

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:baseRetain")))
__attribute__((swift_attr("release:baseRelease")))
__attribute__((swift_attr("returns_retained_by_default"))) BaseType {};

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:derivedRetain")))
__attribute__((swift_attr("release:derivedRelease"))) DerivedType
: public BaseType {};

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:derivedRetain2")))
__attribute__((swift_attr("release:derivedRelease2"))) DerivedType2
: public DerivedType {};

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:derivedRetain3")))
__attribute__((swift_attr("release:derivedRelease3"))) __attribute__((
swift_attr("returns_unretained_by_default"))) DerivedTypeOverrideDefault
: public DerivedType {};

BaseType *returnBaseType() { return new BaseType(); }
DerivedType *returnDerivedType() { return new DerivedType(); }
DerivedType2 *returnDerivedType2() { return new DerivedType2(); }
DerivedTypeOverrideDefault *returnDerivedTypeOverrideDefault() {
return new DerivedTypeOverrideDefault();
}

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:bRetain")))
__attribute__((swift_attr("release:bRelease"))) BaseTypeNonDefault {};

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:dRetain")))
__attribute__((swift_attr("release:dRelease"))) DerivedTypeNonDefault
: public BaseTypeNonDefault {};

BaseTypeNonDefault *returnBaseTypeNonDefault() { // expected-warning {{'returnBaseTypeNonDefault' should be annotated with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED as it is returning a SWIFT_SHARED_REFERENCE}}
return new BaseTypeNonDefault();
}
DerivedTypeNonDefault *returnDerivedTypeNonDefault() { // expected-warning {{'returnDerivedTypeNonDefault' should be annotated with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED as it is returning a SWIFT_SHARED_REFERENCE}}
return new DerivedTypeNonDefault();
}

} // namespace DefaultOwnershipInheritance

void baseRetain(DefaultOwnershipInheritance::BaseType *v) {};
void baseRelease(DefaultOwnershipInheritance::BaseType *v) {};

void derivedRetain(DefaultOwnershipInheritance::DerivedType *v) {};
void derivedRelease(DefaultOwnershipInheritance::DerivedType *v) {};

void derivedRetain2(DefaultOwnershipInheritance::DerivedType2 *v) {};
void derivedRelease2(DefaultOwnershipInheritance::DerivedType2 *v) {};

void derivedRetain3(
DefaultOwnershipInheritance::DerivedTypeOverrideDefault *v) {};
void derivedRelease3(
DefaultOwnershipInheritance::DerivedTypeOverrideDefault *v) {};

void bRetain(DefaultOwnershipInheritance::BaseTypeNonDefault *v) {};
void bRelease(DefaultOwnershipInheritance::BaseTypeNonDefault *v) {};

void dRetain(DefaultOwnershipInheritance::DerivedTypeNonDefault *v) {};
void dRelease(DefaultOwnershipInheritance::DerivedTypeNonDefault *v) {};

SWIFT_END_NULLABILITY_ANNOTATIONS
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ let f = FunctionVoidToFRTStruct()
let frt = f()
let nonFrt = NonFRTStruct()
let nonFrtLocalVar1 = global_function_returning_templated_retrun_frt_owned(nonFrt)
let _ = DefaultOwnershipSuppressUnannotatedAPIWarning.returnRefType()
let _ = DefaultOwnershipSuppressUnannotatedAPIWarning.returnRefTypeDefaultRetained()
let _ = DefaultOwnershipInheritance.returnBaseType()
let _ = DefaultOwnershipInheritance.returnDerivedType()
let _ = DefaultOwnershipInheritance.returnDerivedType2()
let _ = DefaultOwnershipInheritance.returnBaseTypeNonDefault()
let _ = DefaultOwnershipInheritance.returnDerivedTypeNonDefault()
Loading