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

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
84 changes: 84 additions & 0 deletions include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "swift/AST/Attr.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/ClangModuleLoader.h"
#include "clang/AST/Attr.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/Support/VirtualFileSystem.h"

Expand Down Expand Up @@ -70,6 +71,7 @@ namespace dependencies {
}

namespace swift {
enum class ResultConvention : uint8_t;
class ASTContext;
class CompilerInvocation;
class ClangImporterOptions;
Expand Down Expand Up @@ -802,6 +804,88 @@ bool isClangNamespace(const DeclContext *dc);
/// specialized class templates are instead imported as unspecialized.
bool isSymbolicCircularBase(const clang::CXXRecordDecl *templatedClass,
const clang::RecordDecl *base);

/// Match a `[[swift_attr("...")]]` annotation on the given Clang decl.
///
/// \param decl The Clang declaration to inspect.
/// \param patterns List of (attribute name, value) pairs.
/// \returns The value for the first matching attribute, or `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;
}

/// Like `matchSwiftAttr`, but also searches C++ base classes.
///
/// \param decl The Clang declaration to inspect.
/// \param patterns List of (attribute name, value) pairs.
/// \returns The matched value from this decl or its bases, or `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)) {
std::optional<T> result;
recordDecl->forallBases([&](const clang::CXXRecordDecl *base) -> bool {
if (auto baseMatch = matchSwiftAttr<T>(base, patterns)) {
result = baseMatch;
return false;
}

return true;
});

return result;
}

return std::nullopt;
}

/// Matches a `swift_attr("...")` on the record type pointed to by the given
/// Clang type, searching base classes if it's a C++ class.
///
/// \param type A Clang pointer type.
/// \param patterns List of attribute name-value pairs to match.
/// \returns Matched value or std::nullopt.
template <typename T>
std::optional<T> matchSwiftAttrOnRecordPtr(
const clang::QualType &type,
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
if (const auto *ptrType = type->getAs<clang::PointerType>()) {
if (const auto *recordDecl = ptrType->getPointeeType()->getAsRecordDecl()) {
return matchSwiftAttrConsideringInheritance<T>(recordDecl, patterns);
}
}
return std::nullopt;
}

/// Determines the C++ reference ownership convention for the return value
/// using `SWIFT_RETURNS_(UN)RETAINED` on the API; falls back to
/// `SWIFT_RETURNED_AS_(UN)RETAINED_BY_DEFAULT` on the pointee record type.
///
/// \param decl The Clang function or method declaration to inspect.
/// \returns Matched `ResultConvention`, or `std::nullopt` if none applies.
std::optional<ResultConvention>
getCxxRefConventionWithAttrs(const clang::Decl *decl);
} // namespace importer

struct ClangInvocationFileMapping {
Expand Down
34 changes: 34 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8794,3 +8794,37 @@ bool importer::isSymbolicCircularBase(const clang::CXXRecordDecl *symbolicClass,
return classTemplate->getCanonicalDecl() ==
specializedBase->getSpecializedTemplate()->getCanonicalDecl();
}

std::optional<ResultConvention>
swift::importer::getCxxRefConventionWithAttrs(const clang::Decl *decl) {
using RC = ResultConvention;

if (auto result =
matchSwiftAttr<RC>(decl, {{"returns_unretained", RC::Unowned},
{"returns_retained", RC::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 (const clang::RecordDecl *record =
ptrType->getPointeeType()->getAsRecordDecl()) {
return matchSwiftAttrConsideringInheritance<RC>(
record, {{"returned_as_unretained_by_default", RC::Unowned},
{"returned_as_retained_by_default", RC::Owned}});
}
}

return std::nullopt;
}
6 changes: 6 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3665,6 +3665,12 @@ namespace {
unannotatedAPIWarningNeeded = true;
}

if (importer::matchSwiftAttrOnRecordPtr<bool>(
retType, {{"returned_as_retained_by_default", true},
{"returned_as_unretained_by_default", true}})) {
unannotatedAPIWarningNeeded = false;
}

if (unannotatedAPIWarningNeeded) {
HeaderLoc loc(decl->getLocation());
Impl.diagnose(loc, diag::no_returns_retained_returns_unretained,
Expand Down
34 changes: 34 additions & 0 deletions lib/ClangImporter/SwiftBridging/swift/bridging
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,40 @@
#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
/// assumed to return an owned (+1) value by default, unless explicitly annotated with
/// SWIFT_RETURNS_UNRETAINED.
///
/// For example:
/// ```c++
/// struct SWIFT_SHARED_REFERENCE(retainFoo, releaseFoo)
/// SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT
/// Foo { ... };
/// ```
///
/// In Swift, C++ APIs returning `Foo*` will be assumed to return an owned
/// value.
#define SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT \
__attribute__((swift_attr("returned_as_retained_by_default")))

/// Applied to a C++ foreign reference type annotated with
/// SWIFT_SHARED_REFERENCE. Indicates that C++ APIs returning this type are
/// assumed to return an unowned (+0) value by default, unless explicitly annotated
/// with SWIFT_RETURNS_RETAINED.
///
/// For example:
/// ```c++
/// struct SWIFT_SHARED_REFERENCE(retainBar, releaseBar)
/// SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT
/// Bar { ... };
/// ```
///
/// In Swift, C++ 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")))

/// 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
23 changes: 9 additions & 14 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1345,23 +1345,18 @@ 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_RETURNED_AS_(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;
}
}
}
}
return std::nullopt;
if (!tl.getLoweredType().isForeignReferenceType())
return std::nullopt;

return importer::getCxxRefConventionWithAttrs(decl);
}
};

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,157 @@ struct Derived : Base {
FRTStruct *_Nonnull VirtualMethodReturningFRTUnowned() override
__attribute__((swift_attr("returns_unretained")));
};

SWIFT_BEGIN_NULLABILITY_ANNOTATIONS

namespace DefaultOwnershipConventionOnCXXForeignRefType {
struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defRetain1")))
__attribute__((swift_attr("release:defRelease1")))
__attribute__((swift_attr("returned_as_retained_by_default"))) RefTyDefRetained {};

RefTyDefRetained *returnRefTyDefRetained() { return new RefTyDefRetained(); }

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defRetain2")))
__attribute__((swift_attr("release:defRelease2")))
__attribute__((swift_attr("returned_as_unretained_by_default"))) RefTyDefUnretained {};

RefTyDefUnretained *returnRefTyDefUnretained() {
return new RefTyDefUnretained();
}
} // namespace DefaultOwnershipConventionOnCXXForeignRefType

void defRetain1(
DefaultOwnershipConventionOnCXXForeignRefType::RefTyDefRetained *v) {};
void defRelease1(
DefaultOwnershipConventionOnCXXForeignRefType::RefTyDefRetained *v) {};

void defRetain2(
DefaultOwnershipConventionOnCXXForeignRefType::RefTyDefUnretained *v) {};
void defRelease2(
DefaultOwnershipConventionOnCXXForeignRefType::RefTyDefUnretained *v) {};

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

RefTyDefUnretained *returnRefTyDefUnretained() {
return new RefTyDefUnretained();
}
RefTyDefUnretained *returnRefTyDefUnretainedAnnotatedRetained()
__attribute__((swift_attr("returns_retained"))) {
return new RefTyDefUnretained();
}

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:defaultRetain2")))
__attribute__((swift_attr("release:defaultRelease2")))
__attribute__((swift_attr("returned_as_retained_by_default"))) RefTyDefRetained {};

RefTyDefRetained *returnRefTyDefRetained() { return new RefTyDefRetained(); }
RefTyDefRetained *returnRefTyDefRetainedAnnotatedUnRetained()
__attribute__((swift_attr("returns_unretained"))) {
return new RefTyDefRetained();
}

} // namespace FunctionAnnotationHasPrecedence

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

void defaultRetain2(FunctionAnnotationHasPrecedence::RefTyDefRetained *v) {};
void defaultRelease2(FunctionAnnotationHasPrecedence::RefTyDefRetained *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("returned_as_retained_by_default"))) RefTyDefRetained {};

RefTyDefRetained *returnRefTyDefRetained() { return new RefTyDefRetained(); }

} // namespace DefaultOwnershipSuppressUnannotatedAPIWarning

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

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

namespace DefaultOwnershipInheritance {

struct __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:baseRetain")))
__attribute__((swift_attr("release:baseRelease")))
__attribute__((swift_attr("returned_as_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("returned_as_unretained_by_default"))) DerivedOverride
: public DerivedType {};

BaseType *returnBaseType() { return new BaseType(); }
DerivedType *returnDerivedType() { return new DerivedType(); }
DerivedType2 *returnDerivedType2() { return new DerivedType2(); }
DerivedOverride *returnDerivedOverride() { return new DerivedOverride(); }

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::DerivedOverride *v) {};
void derivedRelease3(DefaultOwnershipInheritance::DerivedOverride *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
Loading