Skip to content

Commit eef4780

Browse files
authored
[cxx-interop] Fix type-level ownership annotations for C++ APIs returning reference to frt (#84480)
Type-level ownership annotations (`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`) were not suppressing warning for C++ functions (which are not annotated with `SWIFT_RETURNS_(UN)RETAIND`) returning references (`&`) to foreign reference types. The annotation only worked for pointer returns (`*`), forcing developers to manually annotate every function returning a reference. This PR fixes this problem by changing the logic of `matchSwiftAttrOnRecordPtr` to consider `ReferenceType` also along with `PointerType`. This PR also adds comprehensive lit tests for ownership conventions and diagnostics related to C++ functions that return references to foreign reference types. These tests were previously not covered by our lit tests. rdar://161226212
1 parent 7ad9182 commit eef4780

File tree

5 files changed

+153
-4
lines changed

5 files changed

+153
-4
lines changed

include/swift/ClangImporter/ClangImporter.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -832,17 +832,24 @@ std::optional<T> matchSwiftAttrConsideringInheritance(
832832
/// Matches a `swift_attr("...")` on the record type pointed to by the given
833833
/// Clang type, searching base classes if it's a C++ class.
834834
///
835-
/// \param type A Clang pointer type.
835+
/// \param type A Clang pointer or reference type.
836836
/// \param patterns List of attribute name-value pairs to match.
837837
/// \returns Matched value or std::nullopt.
838838
template <typename T>
839839
std::optional<T> matchSwiftAttrOnRecordPtr(
840840
const clang::QualType &type,
841841
llvm::ArrayRef<std::pair<llvm::StringRef, T>> patterns) {
842+
clang::QualType pointeeType;
842843
if (const auto *ptrType = type->getAs<clang::PointerType>()) {
843-
if (const auto *recordDecl = ptrType->getPointeeType()->getAsRecordDecl()) {
844-
return matchSwiftAttrConsideringInheritance<T>(recordDecl, patterns);
845-
}
844+
pointeeType = ptrType->getPointeeType();
845+
} else if (const auto *refType = type->getAs<clang::ReferenceType>()) {
846+
pointeeType = refType->getPointeeType();
847+
} else {
848+
return std::nullopt;
849+
}
850+
851+
if (const auto *recordDecl = pointeeType->getAsRecordDecl()) {
852+
return matchSwiftAttrConsideringInheritance<T>(recordDecl, patterns);
846853
}
847854
return std::nullopt;
848855
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#pragma once
2+
3+
namespace NoAnnotations {
4+
5+
struct RefCountedType {
6+
int value;
7+
} __attribute__((swift_attr("import_reference")))
8+
__attribute__((swift_attr("retain:retainRefCounted")))
9+
__attribute__((swift_attr("release:releaseRefCounted")));
10+
11+
RefCountedType& getRefCountedByRef(); // expected-note {{'getRefCountedByRef()' is defined here}}
12+
RefCountedType& createRefCountedByRef(); // expected-note {{'createRefCountedByRef()' is defined here}}
13+
RefCountedType& copyRefCountedByRef(); // expected-note {{'copyRefCountedByRef()' is defined here}}
14+
15+
} // namespace NoAnnotations
16+
17+
void retainRefCounted(NoAnnotations::RefCountedType* obj);
18+
void releaseRefCounted(NoAnnotations::RefCountedType* obj);
19+
20+
namespace APIAnnotations {
21+
22+
struct RefCountedType {
23+
int value;
24+
} __attribute__((swift_attr("import_reference")))
25+
__attribute__((swift_attr("retain:retainAPIRefCounted")))
26+
__attribute__((swift_attr("release:releaseAPIRefCounted")));
27+
28+
RefCountedType& createByRef() __attribute__((swift_attr("returns_retained")));
29+
RefCountedType& getByRef() __attribute__((swift_attr("returns_unretained")));
30+
31+
32+
} // namespace APIAnnotations
33+
34+
void retainAPIRefCounted(APIAnnotations::RefCountedType* obj);
35+
void releaseAPIRefCounted(APIAnnotations::RefCountedType* obj);
36+
37+
namespace TypeAnnotation {
38+
39+
struct RefCountedType {
40+
int value;
41+
} __attribute__((swift_attr("import_reference")))
42+
__attribute__((swift_attr("retain:retainTypeRefCounted")))
43+
__attribute__((swift_attr("release:releaseTypeRefCounted")))
44+
__attribute__((swift_attr("returned_as_unretained_by_default")));
45+
46+
RefCountedType& getByRef();
47+
RefCountedType& createByRef();
48+
RefCountedType& copyByRef();
49+
50+
} // namespace TypeAnnotation
51+
52+
void retainTypeRefCounted(TypeAnnotation::RefCountedType* obj);
53+
void releaseTypeRefCounted(TypeAnnotation::RefCountedType* obj);
54+
55+
namespace BothAnnotations {
56+
57+
struct RefCountedType {
58+
int value;
59+
} __attribute__((swift_attr("import_reference")))
60+
__attribute__((swift_attr("retain:retainBothRefCounted")))
61+
__attribute__((swift_attr("release:releaseBothRefCounted")))
62+
__attribute__((swift_attr("returned_as_unretained_by_default")));
63+
64+
// Note: Type default at type level is unretained, but API annotation overrides
65+
RefCountedType& createByRef() __attribute__((swift_attr("returns_retained")));
66+
RefCountedType& getByRef();
67+
68+
} // namespace BothAnnotations
69+
70+
void retainBothRefCounted(BothAnnotations::RefCountedType* obj);
71+
void releaseBothRefCounted(BothAnnotations::RefCountedType* obj);

test/Interop/Cxx/foreign-reference/Inputs/module.modulemap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,8 @@ module LoggingFrts {
8787
header "logging-frts.h"
8888
requires cplusplus
8989
}
90+
91+
module FRTReferenceReturns {
92+
header "frt-reference-returns.h"
93+
requires cplusplus
94+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: rm -rf %t
2+
// RUN: %target-typecheck-verify-swift -I %S%{fs-sep}Inputs -cxx-interoperability-mode=default -enable-experimental-feature WarnUnannotatedReturnOfCxxFrt -verify-additional-file %S%{fs-sep}Inputs%{fs-sep}frt-reference-returns.h
3+
4+
// REQUIRES: swift_feature_WarnUnannotatedReturnOfCxxFrt
5+
6+
import FRTReferenceReturns
7+
8+
func testNoAnnotations() {
9+
let _ = NoAnnotations.getRefCountedByRef()
10+
// expected-warning@-1 {{cannot infer the ownership of the returned value, annotate 'getRefCountedByRef()' with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED}}
11+
let _ = NoAnnotations.createRefCountedByRef()
12+
// expected-warning@-1 {{cannot infer the ownership of the returned value, annotate 'createRefCountedByRef()' with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED}}
13+
let _ = NoAnnotations.copyRefCountedByRef()
14+
// expected-warning@-1 {{cannot infer the ownership of the returned value, annotate 'copyRefCountedByRef()' with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED}}
15+
}
16+
17+
func testAPIAnnotations() {
18+
let _ = APIAnnotations.createByRef()
19+
let _ = APIAnnotations.getByRef()
20+
}
21+
22+
func testTypeAnnotation() {
23+
let _ = TypeAnnotation.getByRef()
24+
let _ = TypeAnnotation.createByRef()
25+
let _ = TypeAnnotation.copyByRef()
26+
}
27+
28+
func testBothAnnotations() {
29+
let _ = BothAnnotations.createByRef()
30+
let _ = BothAnnotations.getByRef()
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %target-swift-emit-sil -I %S/Inputs -cxx-interoperability-mode=default -disable-availability-checking -diagnostic-style llvm %s -validate-tbd-against-ir=none -Xcc -fignore-exceptions | %FileCheck %s
2+
3+
import FRTReferenceReturns
4+
5+
// Test that the ownership annotations work correctly for reference returns
6+
7+
func useAll() {
8+
let _ = NoAnnotations.getRefCountedByRef()
9+
// CHECK: sil [clang NoAnnotations.getRefCountedByRef] @{{.*}} : $@convention(c) () -> NoAnnotations.RefCountedType
10+
// Note: create/copy rule (default without ownership annotations) does not apply if return type is & to frt
11+
let _ = NoAnnotations.createRefCountedByRef()
12+
// CHECK: sil [clang NoAnnotations.createRefCountedByRef] @{{.*}} : $@convention(c) () -> NoAnnotations.RefCountedType
13+
let _ = NoAnnotations.copyRefCountedByRef()
14+
// CHECK: sil [clang NoAnnotations.copyRefCountedByRef] @{{.*}} : $@convention(c) () -> NoAnnotations.RefCountedType
15+
16+
// APIAnnotations - explicit ownership
17+
let _ = APIAnnotations.createByRef()
18+
// CHECK: sil [clang APIAnnotations.createByRef] @{{.*}} : $@convention(c) () -> @owned APIAnnotations.RefCountedType
19+
let _ = APIAnnotations.getByRef()
20+
// CHECK: sil [clang APIAnnotations.getByRef] @{{.*}} : $@convention(c) () -> APIAnnotations.RefCountedType
21+
22+
// TypeAnnotation - all unretained due to type default
23+
let _ = TypeAnnotation.getByRef()
24+
// CHECK: sil [clang TypeAnnotation.getByRef] @{{.*}} : $@convention(c) () -> TypeAnnotation.RefCountedType
25+
let _ = TypeAnnotation.createByRef()
26+
// CHECK: sil [clang TypeAnnotation.createByRef] @{{.*}} : $@convention(c) () -> TypeAnnotation.RefCountedType
27+
let _ = TypeAnnotation.copyByRef()
28+
// CHECK: sil [clang TypeAnnotation.copyByRef] @{{.*}} : $@convention(c) () -> TypeAnnotation.RefCountedType
29+
30+
// BothAnnotations - API overrides type
31+
let _ = BothAnnotations.createByRef()
32+
// CHECK: sil [clang BothAnnotations.createByRef] @{{.*}} : $@convention(c) () -> @owned BothAnnotations.RefCountedType
33+
let _ = BothAnnotations.getByRef()
34+
// CHECK: sil [clang BothAnnotations.getByRef] @{{.*}} : $@convention(c) () -> BothAnnotations.RefCountedType
35+
}

0 commit comments

Comments
 (0)