[clang] [APINotes] Support C++ tag conformances to Swift protocols (PR #102664)

Egor Zhdan via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 9 12:12:37 PDT 2024


https://github.com/egorzhdan created https://github.com/llvm/llvm-project/pull/102664

This allows adding a Clang attribute `swift_attr("conforms_to:ModuleName.ProtocolName")` to C++ structs via API Notes.

The Swift compiler respects this attribute when importing C++ types into Swift by automatically declaring the C++ type as a conforming type to the given Swift protocol.

rdar://131388824

>From 32d62daa94c30b1ddf95fc78e48866c8d4633bbb Mon Sep 17 00:00:00 2001
From: Egor Zhdan <e_zhdan at apple.com>
Date: Fri, 9 Aug 2024 20:08:43 +0100
Subject: [PATCH] [APINotes] Support C++ tag conformances to Swift protocols

This allows adding a Clang attribute `swift_attr("conforms_to:ModuleName.ProtocolName")` to C++ structs via API Notes.

The Swift compiler respects this attribute when importing C++ types into Swift by automatically declaring the C++ type as a conforming type to the given Swift protocol.

rdar://131388824
---
 clang/docs/APINotes.rst                              | 12 ++++++++++++
 clang/include/clang/APINotes/Types.h                 |  6 ++++++
 clang/lib/APINotes/APINotesFormat.h                  |  2 +-
 clang/lib/APINotes/APINotesReader.cpp                |  7 +++++++
 clang/lib/APINotes/APINotesWriter.cpp                |  7 +++++++
 clang/lib/APINotes/APINotesYAMLCompiler.cpp          |  4 ++++
 clang/lib/Sema/SemaAPINotes.cpp                      |  4 ++++
 .../APINotes/Inputs/Headers/SwiftImportAs.apinotes   |  2 ++
 clang/test/APINotes/swift-import-as.cpp              |  2 ++
 9 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/clang/docs/APINotes.rst b/clang/docs/APINotes.rst
index bc09b16bab5d27..dcefa6810dac67 100644
--- a/clang/docs/APINotes.rst
+++ b/clang/docs/APINotes.rst
@@ -188,6 +188,18 @@ declaration kind), all of which are optional:
     - Name: tzdb
       SwiftCopyable: false
 
+:SwiftConformsTo:
+
+  Allows annotating a C++ class as conforming to a Swift protocol. Equivalent
+  to ``SWIFT_CONFORMS_TO_PROTOCOL``. The value is a module-qualified name of a
+  Swift protocol.
+
+  ::
+
+    Tags:
+    - Name: vector
+      SwiftConformsTo: Cxx.CxxSequence
+
 :Availability, AvailabilityMsg:
 
   A value of "nonswift" is equivalent to ``NS_SWIFT_UNAVAILABLE``. A value of
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index c8e5e4df25d173..dc725fbe867b90 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -685,6 +685,8 @@ class TagInfo : public CommonTypeInfo {
   std::optional<std::string> SwiftRetainOp;
   std::optional<std::string> SwiftReleaseOp;
 
+  std::optional<std::string> SwiftConformsTo;
+
   std::optional<EnumExtensibilityKind> EnumExtensibility;
 
   TagInfo()
@@ -720,6 +722,9 @@ class TagInfo : public CommonTypeInfo {
     if (!SwiftReleaseOp)
       SwiftReleaseOp = RHS.SwiftReleaseOp;
 
+    if (!SwiftConformsTo)
+      SwiftConformsTo = RHS.SwiftConformsTo;
+
     if (!HasFlagEnum)
       setFlagEnum(RHS.isFlagEnum());
 
@@ -742,6 +747,7 @@ inline bool operator==(const TagInfo &LHS, const TagInfo &RHS) {
          LHS.SwiftImportAs == RHS.SwiftImportAs &&
          LHS.SwiftRetainOp == RHS.SwiftRetainOp &&
          LHS.SwiftReleaseOp == RHS.SwiftReleaseOp &&
+         LHS.SwiftConformsTo == RHS.SwiftConformsTo &&
          LHS.isFlagEnum() == RHS.isFlagEnum() &&
          LHS.isSwiftCopyable() == RHS.isSwiftCopyable() &&
          LHS.EnumExtensibility == RHS.EnumExtensibility;
diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index 9d254dcc1c9eff..fba5f4e8907dae 100644
--- a/clang/lib/APINotes/APINotesFormat.h
+++ b/clang/lib/APINotes/APINotesFormat.h
@@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0;
 /// API notes file minor version number.
 ///
 /// When the format changes IN ANY WAY, this number should be incremented.
-const uint16_t VERSION_MINOR = 28; // nested tags
+const uint16_t VERSION_MINOR = 29; // SwiftConformsTo
 
 const uint8_t kSwiftCopyable = 1;
 const uint8_t kSwiftNonCopyable = 2;
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 871f782511d5f1..0a0c249b860c71 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -572,6 +572,13 @@ class TagTableInfo
                                         ReleaseOpLength - 1);
       Data += ReleaseOpLength - 1;
     }
+    unsigned ConformsToLength =
+        endian::readNext<uint16_t, llvm::endianness::little>(Data);
+    if (ConformsToLength > 0) {
+      Info.SwiftConformsTo = std::string(reinterpret_cast<const char *>(Data),
+                                         ConformsToLength - 1);
+      Data += ConformsToLength - 1;
+    }
 
     ReadCommonTypeInfo(Data, Info);
     return Info;
diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp
index 2a71922746ac5d..d81bfd9f61759c 100644
--- a/clang/lib/APINotes/APINotesWriter.cpp
+++ b/clang/lib/APINotes/APINotesWriter.cpp
@@ -1189,6 +1189,7 @@ class TagTableInfo : public CommonTypeTableInfo<TagTableInfo, TagInfo> {
     return 2 + (TI.SwiftImportAs ? TI.SwiftImportAs->size() : 0) +
            2 + (TI.SwiftRetainOp ? TI.SwiftRetainOp->size() : 0) +
            2 + (TI.SwiftReleaseOp ? TI.SwiftReleaseOp->size() : 0) +
+           2 + (TI.SwiftConformsTo ? TI.SwiftConformsTo->size() : 0) +
            2 + getCommonTypeInfoSize(TI);
   }
 
@@ -1230,6 +1231,12 @@ class TagTableInfo : public CommonTypeTableInfo<TagTableInfo, TagInfo> {
     } else {
       writer.write<uint16_t>(0);
     }
+    if (auto ConformsTo = TI.SwiftConformsTo) {
+      writer.write<uint16_t>(ConformsTo->size() + 1);
+      OS.write(ConformsTo->c_str(), ConformsTo->size());
+    } else {
+      writer.write<uint16_t>(0);
+    }
 
     emitCommonTypeInfo(OS, TI);
   }
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 11cccc94a15f03..a7d5074910deaa 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -419,6 +419,7 @@ struct Tag {
   std::optional<std::string> SwiftImportAs;
   std::optional<std::string> SwiftRetainOp;
   std::optional<std::string> SwiftReleaseOp;
+  std::optional<std::string> SwiftConformsTo;
   std::optional<EnumExtensibilityKind> EnumExtensibility;
   std::optional<bool> FlagEnum;
   std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
@@ -456,6 +457,7 @@ template <> struct MappingTraits<Tag> {
     IO.mapOptional("SwiftImportAs", T.SwiftImportAs);
     IO.mapOptional("SwiftReleaseOp", T.SwiftReleaseOp);
     IO.mapOptional("SwiftRetainOp", T.SwiftRetainOp);
+    IO.mapOptional("SwiftConformsTo", T.SwiftConformsTo);
     IO.mapOptional("EnumExtensibility", T.EnumExtensibility);
     IO.mapOptional("FlagEnum", T.FlagEnum);
     IO.mapOptional("EnumKind", T.EnumConvenienceKind);
@@ -920,6 +922,8 @@ class YAMLConverter {
       TI.SwiftRetainOp = T.SwiftRetainOp;
     if (T.SwiftReleaseOp)
       TI.SwiftReleaseOp = T.SwiftReleaseOp;
+    if (T.SwiftConformsTo)
+      TI.SwiftConformsTo = T.SwiftConformsTo;
 
     if (T.SwiftCopyable)
       TI.setSwiftCopyable(T.SwiftCopyable);
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 2c49c1f64b2da8..0db94ab54e34e5 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -605,6 +605,10 @@ static void ProcessAPINotes(Sema &S, TagDecl *D, const api_notes::TagInfo &Info,
     D->addAttr(
         SwiftAttrAttr::Create(S.Context, "release:" + ReleaseOp.value()));
 
+  if (auto ConformsTo = Info.SwiftConformsTo)
+    D->addAttr(
+        SwiftAttrAttr::Create(S.Context, "conforms_to:" + ConformsTo.value()));
+
   if (auto Copyable = Info.isSwiftCopyable()) {
     if (!*Copyable)
       D->addAttr(SwiftAttrAttr::Create(S.Context, "~Copyable"));
diff --git a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes
index b0eead42869a41..f4f9c7a244e0a3 100644
--- a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes
+++ b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes
@@ -7,7 +7,9 @@ Tags:
   SwiftImportAs: reference
   SwiftReleaseOp: RCRelease
   SwiftRetainOp: RCRetain
+  SwiftConformsTo: MySwiftModule.MySwiftRefCountedProtocol
 - Name: NonCopyableType
   SwiftCopyable: false
+  SwiftConformsTo: MySwiftModule.MySwiftNonCopyableProtocol
 - Name: CopyableType
   SwiftCopyable: true
diff --git a/clang/test/APINotes/swift-import-as.cpp b/clang/test/APINotes/swift-import-as.cpp
index 62e6450e94e113..6457e1557618de 100644
--- a/clang/test/APINotes/swift-import-as.cpp
+++ b/clang/test/APINotes/swift-import-as.cpp
@@ -16,9 +16,11 @@
 // CHECK-REF-COUNTED: SwiftAttrAttr {{.+}} <<invalid sloc>> "import_reference"
 // CHECK-REF-COUNTED: SwiftAttrAttr {{.+}} <<invalid sloc>> "retain:RCRetain"
 // CHECK-REF-COUNTED: SwiftAttrAttr {{.+}} <<invalid sloc>> "release:RCRelease"
+// CHECK-REF-COUNTED: SwiftAttrAttr {{.+}} <<invalid sloc>> "conforms_to:MySwiftModule.MySwiftRefCountedProtocol"
 
 // CHECK-NON-COPYABLE: Dumping NonCopyableType:
 // CHECK-NON-COPYABLE-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs {{.+}} struct NonCopyableType
+// CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <<invalid sloc>> "conforms_to:MySwiftModule.MySwiftNonCopyableProtocol"
 // CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <<invalid sloc>> "~Copyable"
 
 // CHECK-COPYABLE: Dumping CopyableType:



More information about the cfe-commits mailing list