-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
base: main
Are you sure you want to change the base?
Changes from all commits
0bc94e9
6e776be
b0fbff0
f52ef8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
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 But in this case, when we annotate an FRT Thus, my suggestion is to rename this annotation to something like |
||||||||||||||||||||||||||
__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. | ||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1252,6 +1252,49 @@ enum class ConventionsKind : uint8_t { | |||||
CXXMethod = 8, | ||||||
}; | ||||||
|
||||||
template <typename T> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
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 = | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you should be able to do |
||||||
baseSpecifier.getType()->getAsCXXRecordDecl(); | ||||||
if (!baseDecl) | ||||||
continue; | ||||||
if (auto match = | ||||||
matchSwiftAttrConsideringInheritance<T>(baseDecl, patterns)) | ||||||
return match; | ||||||
} | ||||||
} | ||||||
|
||||||
return std::nullopt; | ||||||
} | ||||||
|
||||||
class Conventions { | ||||||
ConventionsKind kind; | ||||||
|
||||||
|
@@ -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 = | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
ptrType->getPointeeType()->getAsRecordDecl()) | ||||||
return matchSwiftAttrConsideringInheritance<ResultConvention>( | ||||||
clangRecordDecl, | ||||||
{{"returns_unretained_by_default", ResultConvention::Unowned}, | ||||||
{"returns_retained_by_default", ResultConvention::Owned}}); | ||||||
} | ||||||
return std::nullopt; | ||||||
} | ||||||
|
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 | ||||||
|
@@ -326,3 +327,175 @@ struct Derived : Base { | |||||
FRTStruct *_Nonnull VirtualMethodReturningFRTUnowned() override | ||||||
__attribute__((swift_attr("returns_unretained"))); | ||||||
}; | ||||||
|
||||||
SWIFT_BEGIN_NULLABILITY_ANNOTATIONS | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
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 |
There was a problem hiding this comment.
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 needif (auto match = condition)
; justif (condition)
should suffice.