[clang] 8a79dc7 - [APINotes] Support annotating C++ methods

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 19 05:35:16 PDT 2024


Author: Egor Zhdan
Date: 2024-07-19T13:35:13+01:00
New Revision: 8a79dc7e6f765f3f49c5dd9330fc0826d3362858

URL: https://github.com/llvm/llvm-project/commit/8a79dc7e6f765f3f49c5dd9330fc0826d3362858
DIFF: https://github.com/llvm/llvm-project/commit/8a79dc7e6f765f3f49c5dd9330fc0826d3362858.diff

LOG: [APINotes] Support annotating C++ methods

This adds support for adding Clang attributes to C++ methods declared
within C++ records by using API Notes.

For instance:
```
Tags:
- Name: IntWrapper
  Methods:
  - Name: getIncremented
    Availability: none
```

This is the first instance of something within a C++ record being
annotated with API Notes, so it adds the necessary infra to make a C++
record an "API Notes context".

Notably this does not add support for nested C++ tags. That will be
added in a follow-up patch.

rdar://131387880

Added: 
    clang/test/APINotes/Inputs/Headers/Methods.apinotes
    clang/test/APINotes/Inputs/Headers/Methods.h
    clang/test/APINotes/methods.cpp

Modified: 
    clang/include/clang/APINotes/APINotesReader.h
    clang/include/clang/APINotes/APINotesWriter.h
    clang/include/clang/APINotes/Types.h
    clang/lib/APINotes/APINotesFormat.h
    clang/lib/APINotes/APINotesReader.cpp
    clang/lib/APINotes/APINotesWriter.cpp
    clang/lib/APINotes/APINotesYAMLCompiler.cpp
    clang/lib/Sema/SemaAPINotes.cpp
    clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
    clang/test/APINotes/Inputs/Headers/Namespaces.h
    clang/test/APINotes/Inputs/Headers/module.modulemap
    clang/test/APINotes/namespaces.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 37a4ff7a69712..03657352c49a5 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -141,6 +141,16 @@ class APINotesReader {
                                                  ObjCSelectorRef Selector,
                                                  bool IsInstanceMethod);
 
+  /// Look for information regarding the given C++ method in the given C++ tag
+  /// context.
+  ///
+  /// \param CtxID The ID that references the parent context, i.e. a C++ tag.
+  /// \param Name The name of the C++ method we're looking for.
+  ///
+  /// \returns Information about the method, if known.
+  VersionedInfo<CXXMethodInfo> lookupCXXMethod(ContextID CtxID,
+                                               llvm::StringRef Name);
+
   /// Look for information regarding the given global variable.
   ///
   /// \param Name The name of the global variable.
@@ -166,6 +176,17 @@ class APINotesReader {
   /// \returns information about the enumerator, if known.
   VersionedInfo<EnumConstantInfo> lookupEnumConstant(llvm::StringRef Name);
 
+  /// Look for the context ID of the given C++ tag.
+  ///
+  /// \param Name The name of the tag we're looking for.
+  /// \param ParentCtx The context in which this tag is declared, e.g. a C++
+  /// namespace.
+  ///
+  /// \returns The ID, if known.
+  std::optional<ContextID>
+  lookupTagID(llvm::StringRef Name,
+              std::optional<Context> ParentCtx = std::nullopt);
+
   /// Look for information regarding the given tag
   /// (struct/union/enum/C++ class).
   ///

diff  --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h
index e82dbc7c9540e..e0fe5eacef725 100644
--- a/clang/include/clang/APINotes/APINotesWriter.h
+++ b/clang/include/clang/APINotes/APINotesWriter.h
@@ -78,6 +78,14 @@ class APINotesWriter {
                      bool IsInstanceMethod, const ObjCMethodInfo &Info,
                      llvm::VersionTuple SwiftVersion);
 
+  /// Add information about a specific C++ method.
+  ///
+  /// \param CtxID The context in which this method resides, i.e. a C++ tag.
+  /// \param Name The name of the method.
+  /// \param Info Information about this method.
+  void addCXXMethod(ContextID CtxID, llvm::StringRef Name,
+                    const CXXMethodInfo &Info, llvm::VersionTuple SwiftVersion);
+
   /// Add information about a global variable.
   ///
   /// \param Name The name of this global variable.

diff  --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index b389aa8d56f16..c8e5e4df25d17 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -656,6 +656,12 @@ class GlobalFunctionInfo : public FunctionInfo {
   GlobalFunctionInfo() {}
 };
 
+/// Describes API notes data for a C++ method.
+class CXXMethodInfo : public FunctionInfo {
+public:
+  CXXMethodInfo() {}
+};
+
 /// Describes API notes data for an enumerator.
 class EnumConstantInfo : public CommonEntityInfo {
 public:
@@ -789,6 +795,7 @@ enum class ContextKind : uint8_t {
   ObjCClass = 0,
   ObjCProtocol = 1,
   Namespace = 2,
+  Tag = 3,
 };
 
 struct Context {

diff  --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index 42dfe7a773a97..cd6456dbe37b2 100644
--- a/clang/lib/APINotes/APINotesFormat.h
+++ b/clang/lib/APINotes/APINotesFormat.h
@@ -63,6 +63,10 @@ enum BlockID {
   /// about the method.
   OBJC_METHOD_BLOCK_ID,
 
+  /// The C++ method data block, which maps C++ (context id, method name) pairs
+  /// to information about the method.
+  CXX_METHOD_BLOCK_ID,
+
   /// The Objective-C selector data block, which maps Objective-C
   /// selector names (# of pieces, identifier IDs) to the selector ID
   /// used in other tables.
@@ -181,6 +185,20 @@ using ObjCMethodDataLayout =
                          >;
 } // namespace objc_method_block
 
+namespace cxx_method_block {
+enum {
+  CXX_METHOD_DATA = 1,
+};
+
+using CXXMethodDataLayout =
+    llvm::BCRecordLayout<CXX_METHOD_DATA, // record ID
+                         llvm::BCVBR<16>, // table offset within the blob (see
+                                          // below)
+                         llvm::BCBlob     // map from C++ (context id, name)
+                                          // tuples to C++ method information
+                         >;
+} // namespace cxx_method_block
+
 namespace objc_selector_block {
 enum {
   OBJC_SELECTOR_DATA = 1,
@@ -269,6 +287,17 @@ struct ContextTableKey {
       : parentContextID(parentContextID), contextKind(contextKind),
         contextID(contextID) {}
 
+  ContextTableKey(std::optional<ContextID> ParentContextID, ContextKind Kind,
+                  uint32_t ContextID)
+      : parentContextID(ParentContextID ? ParentContextID->Value : -1),
+        contextKind(static_cast<uint8_t>(Kind)), contextID(ContextID) {}
+
+  ContextTableKey(std::optional<Context> ParentContext, ContextKind Kind,
+                  uint32_t ContextID)
+      : ContextTableKey(ParentContext ? std::make_optional(ParentContext->id)
+                                      : std::nullopt,
+                        Kind, ContextID) {}
+
   llvm::hash_code hashValue() const {
     return llvm::hash_value(
         std::tuple{parentContextID, contextKind, contextID});

diff  --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 7600738374840..871f782511d5f 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -473,6 +473,29 @@ class GlobalFunctionTableInfo
   }
 };
 
+/// Used to deserialize the on-disk C++ method table.
+class CXXMethodTableInfo
+    : public VersionedTableInfo<CXXMethodTableInfo, SingleDeclTableKey,
+                                CXXMethodInfo> {
+public:
+  static internal_key_type ReadKey(const uint8_t *Data, unsigned Length) {
+    auto CtxID = endian::readNext<uint32_t, llvm::endianness::little>(Data);
+    auto NameID = endian::readNext<uint32_t, llvm::endianness::little>(Data);
+    return {CtxID, NameID};
+  }
+
+  hash_value_type ComputeHash(internal_key_type Key) {
+    return static_cast<size_t>(Key.hashValue());
+  }
+
+  static CXXMethodInfo readUnversioned(internal_key_type Key,
+                                       const uint8_t *&Data) {
+    CXXMethodInfo Info;
+    ReadFunctionInfo(Data, Info);
+    return Info;
+  }
+};
+
 /// Used to deserialize the on-disk enumerator table.
 class EnumConstantTableInfo
     : public VersionedTableInfo<EnumConstantTableInfo, uint32_t,
@@ -630,6 +653,12 @@ class APINotesReader::Implementation {
   /// The Objective-C method table.
   std::unique_ptr<SerializedObjCMethodTable> ObjCMethodTable;
 
+  using SerializedCXXMethodTable =
+      llvm::OnDiskIterableChainedHashTable<CXXMethodTableInfo>;
+
+  /// The C++ method table.
+  std::unique_ptr<SerializedCXXMethodTable> CXXMethodTable;
+
   using SerializedObjCSelectorTable =
       llvm::OnDiskIterableChainedHashTable<ObjCSelectorTableInfo>;
 
@@ -683,6 +712,8 @@ class APINotesReader::Implementation {
                              llvm::SmallVectorImpl<uint64_t> &Scratch);
   bool readObjCMethodBlock(llvm::BitstreamCursor &Cursor,
                            llvm::SmallVectorImpl<uint64_t> &Scratch);
+  bool readCXXMethodBlock(llvm::BitstreamCursor &Cursor,
+                          llvm::SmallVectorImpl<uint64_t> &Scratch);
   bool readObjCSelectorBlock(llvm::BitstreamCursor &Cursor,
                              llvm::SmallVectorImpl<uint64_t> &Scratch);
   bool readGlobalVariableBlock(llvm::BitstreamCursor &Cursor,
@@ -1140,6 +1171,81 @@ bool APINotesReader::Implementation::readObjCMethodBlock(
   return false;
 }
 
+bool APINotesReader::Implementation::readCXXMethodBlock(
+    llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) {
+  if (Cursor.EnterSubBlock(CXX_METHOD_BLOCK_ID))
+    return true;
+
+  llvm::Expected<llvm::BitstreamEntry> MaybeNext = Cursor.advance();
+  if (!MaybeNext) {
+    // FIXME this drops the error on the floor.
+    consumeError(MaybeNext.takeError());
+    return false;
+  }
+  llvm::BitstreamEntry Next = MaybeNext.get();
+  while (Next.Kind != llvm::BitstreamEntry::EndBlock) {
+    if (Next.Kind == llvm::BitstreamEntry::Error)
+      return true;
+
+    if (Next.Kind == llvm::BitstreamEntry::SubBlock) {
+      // Unknown sub-block, possibly for use by a future version of the
+      // API notes format.
+      if (Cursor.SkipBlock())
+        return true;
+
+      MaybeNext = Cursor.advance();
+      if (!MaybeNext) {
+        // FIXME this drops the error on the floor.
+        consumeError(MaybeNext.takeError());
+        return false;
+      }
+      Next = MaybeNext.get();
+      continue;
+    }
+
+    Scratch.clear();
+    llvm::StringRef BlobData;
+    llvm::Expected<unsigned> MaybeKind =
+        Cursor.readRecord(Next.ID, Scratch, &BlobData);
+    if (!MaybeKind) {
+      // FIXME this drops the error on the floor.
+      consumeError(MaybeKind.takeError());
+      return false;
+    }
+    unsigned Kind = MaybeKind.get();
+    switch (Kind) {
+    case cxx_method_block::CXX_METHOD_DATA: {
+      // Already saw C++ method table.
+      if (CXXMethodTable)
+        return true;
+
+      uint32_t tableOffset;
+      cxx_method_block::CXXMethodDataLayout::readRecord(Scratch, tableOffset);
+      auto base = reinterpret_cast<const uint8_t *>(BlobData.data());
+
+      CXXMethodTable.reset(SerializedCXXMethodTable::Create(
+          base + tableOffset, base + sizeof(uint32_t), base));
+      break;
+    }
+
+    default:
+      // Unknown record, possibly for use by a future version of the
+      // module format.
+      break;
+    }
+
+    MaybeNext = Cursor.advance();
+    if (!MaybeNext) {
+      // FIXME this drops the error on the floor.
+      consumeError(MaybeNext.takeError());
+      return false;
+    }
+    Next = MaybeNext.get();
+  }
+
+  return false;
+}
+
 bool APINotesReader::Implementation::readObjCSelectorBlock(
     llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) {
   if (Cursor.EnterSubBlock(OBJC_SELECTOR_BLOCK_ID))
@@ -1692,6 +1798,14 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *InputBuffer,
       }
       break;
 
+    case CXX_METHOD_BLOCK_ID:
+      if (!HasValidControlBlock ||
+          Implementation->readCXXMethodBlock(Cursor, Scratch)) {
+        Failed = true;
+        return;
+      }
+      break;
+
     case OBJC_SELECTOR_BLOCK_ID:
       if (!HasValidControlBlock ||
           Implementation->readObjCSelectorBlock(Cursor, Scratch)) {
@@ -1911,6 +2025,23 @@ auto APINotesReader::lookupObjCMethod(ContextID CtxID, ObjCSelectorRef Selector,
   return {Implementation->SwiftVersion, *Known};
 }
 
+auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name)
+    -> VersionedInfo<CXXMethodInfo> {
+  if (!Implementation->CXXMethodTable)
+    return std::nullopt;
+
+  std::optional<IdentifierID> NameID = Implementation->getIdentifier(Name);
+  if (!NameID)
+    return std::nullopt;
+
+  auto Known = Implementation->CXXMethodTable->find(
+      SingleDeclTableKey(CtxID.Value, *NameID));
+  if (Known == Implementation->CXXMethodTable->end())
+    return std::nullopt;
+
+  return {Implementation->SwiftVersion, *Known};
+}
+
 auto APINotesReader::lookupGlobalVariable(llvm::StringRef Name,
                                           std::optional<Context> Ctx)
     -> VersionedInfo<GlobalVariableInfo> {
@@ -1965,6 +2096,24 @@ auto APINotesReader::lookupEnumConstant(llvm::StringRef Name)
   return {Implementation->SwiftVersion, *Known};
 }
 
+auto APINotesReader::lookupTagID(llvm::StringRef Name,
+                                 std::optional<Context> ParentCtx)
+    -> std::optional<ContextID> {
+  if (!Implementation->ContextIDTable)
+    return std::nullopt;
+
+  std::optional<IdentifierID> TagID = Implementation->getIdentifier(Name);
+  if (!TagID)
+    return std::nullopt;
+
+  auto KnownID = Implementation->ContextIDTable->find(
+      ContextTableKey(ParentCtx, ContextKind::Tag, *TagID));
+  if (KnownID == Implementation->ContextIDTable->end())
+    return std::nullopt;
+
+  return ContextID(*KnownID);
+}
+
 auto APINotesReader::lookupTag(llvm::StringRef Name, std::optional<Context> Ctx)
     -> VersionedInfo<TagInfo> {
   if (!Implementation->TagTable)

diff  --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp
index 1090d3f20df21..2a71922746ac5 100644
--- a/clang/lib/APINotes/APINotesWriter.cpp
+++ b/clang/lib/APINotes/APINotesWriter.cpp
@@ -70,6 +70,13 @@ class APINotesWriter::Implementation {
                  llvm::SmallVector<std::pair<VersionTuple, ObjCMethodInfo>, 1>>
       ObjCMethods;
 
+  /// Information about C++ methods.
+  ///
+  /// Indexed by the context ID and name ID.
+  llvm::DenseMap<SingleDeclTableKey,
+                 llvm::SmallVector<std::pair<VersionTuple, CXXMethodInfo>, 1>>
+      CXXMethods;
+
   /// Mapping from selectors to selector ID.
   llvm::DenseMap<StoredObjCSelector, SelectorID> SelectorIDs;
 
@@ -150,6 +157,7 @@ class APINotesWriter::Implementation {
   void writeContextBlock(llvm::BitstreamWriter &Stream);
   void writeObjCPropertyBlock(llvm::BitstreamWriter &Stream);
   void writeObjCMethodBlock(llvm::BitstreamWriter &Stream);
+  void writeCXXMethodBlock(llvm::BitstreamWriter &Stream);
   void writeObjCSelectorBlock(llvm::BitstreamWriter &Stream);
   void writeGlobalVariableBlock(llvm::BitstreamWriter &Stream);
   void writeGlobalFunctionBlock(llvm::BitstreamWriter &Stream);
@@ -181,6 +189,7 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &OS) {
     writeContextBlock(Stream);
     writeObjCPropertyBlock(Stream);
     writeObjCMethodBlock(Stream);
+    writeCXXMethodBlock(Stream);
     writeObjCSelectorBlock(Stream);
     writeGlobalVariableBlock(Stream);
     writeGlobalFunctionBlock(Stream);
@@ -765,6 +774,34 @@ class ObjCMethodTableInfo
     emitFunctionInfo(OS, OMI);
   }
 };
+
+/// Used to serialize the on-disk C++ method table.
+class CXXMethodTableInfo
+    : public VersionedTableInfo<CXXMethodTableInfo, SingleDeclTableKey,
+                                CXXMethodInfo> {
+public:
+  unsigned getKeyLength(key_type_ref) {
+    return sizeof(uint32_t) + sizeof(uint32_t);
+  }
+
+  void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) {
+    llvm::support::endian::Writer writer(OS, llvm::endianness::little);
+    writer.write<uint32_t>(Key.parentContextID);
+    writer.write<uint32_t>(Key.nameID);
+  }
+
+  hash_value_type ComputeHash(key_type_ref key) {
+    return static_cast<size_t>(key.hashValue());
+  }
+
+  unsigned getUnversionedInfoSize(const CXXMethodInfo &OMI) {
+    return getFunctionInfoSize(OMI);
+  }
+
+  void emitUnversionedInfo(raw_ostream &OS, const CXXMethodInfo &OMI) {
+    emitFunctionInfo(OS, OMI);
+  }
+};
 } // namespace
 
 void APINotesWriter::Implementation::writeObjCMethodBlock(
@@ -794,6 +831,33 @@ void APINotesWriter::Implementation::writeObjCMethodBlock(
   }
 }
 
+void APINotesWriter::Implementation::writeCXXMethodBlock(
+    llvm::BitstreamWriter &Stream) {
+  llvm::BCBlockRAII Scope(Stream, CXX_METHOD_BLOCK_ID, 3);
+
+  if (CXXMethods.empty())
+    return;
+
+  {
+    llvm::SmallString<4096> HashTableBlob;
+    uint32_t Offset;
+    {
+      llvm::OnDiskChainedHashTableGenerator<CXXMethodTableInfo> Generator;
+      for (auto &MD : CXXMethods)
+        Generator.insert(MD.first, MD.second);
+
+      llvm::raw_svector_ostream BlobStream(HashTableBlob);
+      // Make sure that no bucket is at offset 0
+      llvm::support::endian::write<uint32_t>(BlobStream, 0,
+                                             llvm::endianness::little);
+      Offset = Generator.Emit(BlobStream);
+    }
+
+    cxx_method_block::CXXMethodDataLayout CXXMethodData(Stream);
+    CXXMethodData.emit(Scratch, Offset, HashTableBlob);
+  }
+}
+
 namespace {
 /// Used to serialize the on-disk Objective-C selector table.
 class ObjCSelectorTableInfo {
@@ -1344,6 +1408,14 @@ void APINotesWriter::addObjCMethod(ContextID CtxID, ObjCSelectorRef Selector,
   }
 }
 
+void APINotesWriter::addCXXMethod(ContextID CtxID, llvm::StringRef Name,
+                                  const CXXMethodInfo &Info,
+                                  VersionTuple SwiftVersion) {
+  IdentifierID NameID = Implementation->getIdentifier(Name);
+  SingleDeclTableKey Key(CtxID.Value, NameID);
+  Implementation->CXXMethods[Key].push_back({SwiftVersion, Info});
+}
+
 void APINotesWriter::addGlobalVariable(std::optional<Context> Ctx,
                                        llvm::StringRef Name,
                                        const GlobalVariableInfo &Info,

diff  --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 870b64e3b7a9b..060e1fdaf2fd9 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -420,6 +420,7 @@ struct Tag {
   std::optional<bool> FlagEnum;
   std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
   std::optional<bool> SwiftCopyable;
+  FunctionsSeq Methods;
 };
 
 typedef std::vector<Tag> TagsSeq;
@@ -454,6 +455,7 @@ template <> struct MappingTraits<Tag> {
     IO.mapOptional("FlagEnum", T.FlagEnum);
     IO.mapOptional("EnumKind", T.EnumConvenienceKind);
     IO.mapOptional("SwiftCopyable", T.SwiftCopyable);
+    IO.mapOptional("Methods", T.Methods);
   }
 };
 } // namespace yaml
@@ -874,6 +876,96 @@ class YAMLConverter {
                          TheNamespace.Items, SwiftVersion);
   }
 
+  void convertFunction(const Function &Function, FunctionInfo &FI) {
+    convertAvailability(Function.Availability, FI, Function.Name);
+    FI.setSwiftPrivate(Function.SwiftPrivate);
+    FI.SwiftName = std::string(Function.SwiftName);
+    convertParams(Function.Params, FI);
+    convertNullability(Function.Nullability, Function.NullabilityOfRet, FI,
+                       Function.Name);
+    FI.ResultType = std::string(Function.ResultType);
+    FI.setRetainCountConvention(Function.RetainCountConvention);
+  }
+
+  void convertTagContext(std::optional<Context> ParentContext, const Tag &T,
+                         VersionTuple SwiftVersion) {
+    TagInfo TI;
+    std::optional<ContextID> ParentContextID =
+        ParentContext ? std::optional<ContextID>(ParentContext->id)
+                      : std::nullopt;
+    convertCommonType(T, TI, T.Name);
+
+    if ((T.SwiftRetainOp || T.SwiftReleaseOp) && !T.SwiftImportAs) {
+      emitError(llvm::Twine("should declare SwiftImportAs to use "
+                            "SwiftRetainOp and SwiftReleaseOp (for ") +
+                T.Name + ")");
+      return;
+    }
+    if (T.SwiftReleaseOp.has_value() != T.SwiftRetainOp.has_value()) {
+      emitError(llvm::Twine("should declare both SwiftReleaseOp and "
+                            "SwiftRetainOp (for ") +
+                T.Name + ")");
+      return;
+    }
+
+    if (T.SwiftImportAs)
+      TI.SwiftImportAs = T.SwiftImportAs;
+    if (T.SwiftRetainOp)
+      TI.SwiftRetainOp = T.SwiftRetainOp;
+    if (T.SwiftReleaseOp)
+      TI.SwiftReleaseOp = T.SwiftReleaseOp;
+
+    if (T.SwiftCopyable)
+      TI.setSwiftCopyable(T.SwiftCopyable);
+
+    if (T.EnumConvenienceKind) {
+      if (T.EnumExtensibility) {
+        emitError(
+            llvm::Twine("cannot mix EnumKind and EnumExtensibility (for ") +
+            T.Name + ")");
+        return;
+      }
+      if (T.FlagEnum) {
+        emitError(llvm::Twine("cannot mix EnumKind and FlagEnum (for ") +
+                  T.Name + ")");
+        return;
+      }
+      switch (*T.EnumConvenienceKind) {
+      case EnumConvenienceAliasKind::None:
+        TI.EnumExtensibility = EnumExtensibilityKind::None;
+        TI.setFlagEnum(false);
+        break;
+      case EnumConvenienceAliasKind::CFEnum:
+        TI.EnumExtensibility = EnumExtensibilityKind::Open;
+        TI.setFlagEnum(false);
+        break;
+      case EnumConvenienceAliasKind::CFOptions:
+        TI.EnumExtensibility = EnumExtensibilityKind::Open;
+        TI.setFlagEnum(true);
+        break;
+      case EnumConvenienceAliasKind::CFClosedEnum:
+        TI.EnumExtensibility = EnumExtensibilityKind::Closed;
+        TI.setFlagEnum(false);
+        break;
+      }
+    } else {
+      TI.EnumExtensibility = T.EnumExtensibility;
+      TI.setFlagEnum(T.FlagEnum);
+    }
+
+    Writer.addTag(ParentContext, T.Name, TI, SwiftVersion);
+
+    ContextInfo CI;
+    auto TagCtxID = Writer.addContext(ParentContextID, T.Name, ContextKind::Tag,
+                                      CI, SwiftVersion);
+
+    for (const auto &CXXMethod : T.Methods) {
+      CXXMethodInfo MI;
+      convertFunction(CXXMethod, MI);
+      Writer.addCXXMethod(TagCtxID, CXXMethod.Name, MI, SwiftVersion);
+    }
+  }
+
   void convertTopLevelItems(std::optional<Context> Ctx,
                             const TopLevelItems &TLItems,
                             VersionTuple SwiftVersion) {
@@ -950,14 +1042,7 @@ class YAMLConverter {
       }
 
       GlobalFunctionInfo GFI;
-      convertAvailability(Function.Availability, GFI, Function.Name);
-      GFI.setSwiftPrivate(Function.SwiftPrivate);
-      GFI.SwiftName = std::string(Function.SwiftName);
-      convertParams(Function.Params, GFI);
-      convertNullability(Function.Nullability, Function.NullabilityOfRet, GFI,
-                         Function.Name);
-      GFI.ResultType = std::string(Function.ResultType);
-      GFI.setRetainCountConvention(Function.RetainCountConvention);
+      convertFunction(Function, GFI);
       Writer.addGlobalFunction(Ctx, Function.Name, GFI, SwiftVersion);
     }
 
@@ -988,68 +1073,7 @@ class YAMLConverter {
         continue;
       }
 
-      TagInfo TI;
-      convertCommonType(Tag, TI, Tag.Name);
-
-      if ((Tag.SwiftRetainOp || Tag.SwiftReleaseOp) && !Tag.SwiftImportAs) {
-        emitError(llvm::Twine("should declare SwiftImportAs to use "
-                              "SwiftRetainOp and SwiftReleaseOp (for ") +
-                  Tag.Name + ")");
-        continue;
-      }
-      if (Tag.SwiftReleaseOp.has_value() != Tag.SwiftRetainOp.has_value()) {
-        emitError(llvm::Twine("should declare both SwiftReleaseOp and "
-                              "SwiftRetainOp (for ") +
-                  Tag.Name + ")");
-        continue;
-      }
-
-      if (Tag.SwiftImportAs)
-        TI.SwiftImportAs = Tag.SwiftImportAs;
-      if (Tag.SwiftRetainOp)
-        TI.SwiftRetainOp = Tag.SwiftRetainOp;
-      if (Tag.SwiftReleaseOp)
-        TI.SwiftReleaseOp = Tag.SwiftReleaseOp;
-
-      if (Tag.SwiftCopyable)
-        TI.setSwiftCopyable(Tag.SwiftCopyable);
-
-      if (Tag.EnumConvenienceKind) {
-        if (Tag.EnumExtensibility) {
-          emitError(
-              llvm::Twine("cannot mix EnumKind and EnumExtensibility (for ") +
-              Tag.Name + ")");
-          continue;
-        }
-        if (Tag.FlagEnum) {
-          emitError(llvm::Twine("cannot mix EnumKind and FlagEnum (for ") +
-                    Tag.Name + ")");
-          continue;
-        }
-        switch (*Tag.EnumConvenienceKind) {
-        case EnumConvenienceAliasKind::None:
-          TI.EnumExtensibility = EnumExtensibilityKind::None;
-          TI.setFlagEnum(false);
-          break;
-        case EnumConvenienceAliasKind::CFEnum:
-          TI.EnumExtensibility = EnumExtensibilityKind::Open;
-          TI.setFlagEnum(false);
-          break;
-        case EnumConvenienceAliasKind::CFOptions:
-          TI.EnumExtensibility = EnumExtensibilityKind::Open;
-          TI.setFlagEnum(true);
-          break;
-        case EnumConvenienceAliasKind::CFClosedEnum:
-          TI.EnumExtensibility = EnumExtensibilityKind::Closed;
-          TI.setFlagEnum(false);
-          break;
-        }
-      } else {
-        TI.EnumExtensibility = Tag.EnumExtensibility;
-        TI.setFlagEnum(Tag.FlagEnum);
-      }
-
-      Writer.addTag(Ctx, Tag.Name, TI, SwiftVersion);
+      convertTagContext(Ctx, Tag, SwiftVersion);
     }
 
     // Write all typedefs.

diff  --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 3482f3741fce6..055e66a0c3486 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -546,6 +546,13 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc,
                   Metadata);
 }
 
+/// Process API notes for a C++ method.
+static void ProcessAPINotes(Sema &S, CXXMethodDecl *Method,
+                            const api_notes::CXXMethodInfo &Info,
+                            VersionedInfoMetadata Metadata) {
+  ProcessAPINotes(S, (FunctionOrMethod)Method, Info, Metadata);
+}
+
 /// Process API notes for a global function.
 static void ProcessAPINotes(Sema &S, FunctionDecl *D,
                             const api_notes::GlobalFunctionInfo &Info,
@@ -782,13 +789,9 @@ void Sema::ProcessAPINotes(Decl *D) {
   if (!D)
     return;
 
-  // Globals.
-  if (D->getDeclContext()->isFileContext() ||
-      D->getDeclContext()->isNamespace() ||
-      D->getDeclContext()->isExternCContext() ||
-      D->getDeclContext()->isExternCXXContext()) {
-    std::optional<api_notes::Context> APINotesContext;
-    if (auto NamespaceContext = dyn_cast<NamespaceDecl>(D->getDeclContext())) {
+  auto GetNamespaceContext =
+      [&](DeclContext *DC) -> std::optional<api_notes::Context> {
+    if (auto NamespaceContext = dyn_cast<NamespaceDecl>(DC)) {
       for (auto Reader :
            APINotes.findAPINotes(NamespaceContext->getLocation())) {
         // Retrieve the context ID for the parent namespace of the decl.
@@ -811,11 +814,20 @@ void Sema::ProcessAPINotes(Decl *D) {
             break;
         }
         if (NamespaceID)
-          APINotesContext = api_notes::Context(
-              *NamespaceID, api_notes::ContextKind::Namespace);
+          return api_notes::Context(*NamespaceID,
+                                    api_notes::ContextKind::Namespace);
       }
     }
+    return std::nullopt;
+  };
 
+  // Globals.
+  if (D->getDeclContext()->isFileContext() ||
+      D->getDeclContext()->isNamespace() ||
+      D->getDeclContext()->isExternCContext() ||
+      D->getDeclContext()->isExternCXXContext()) {
+    std::optional<api_notes::Context> APINotesContext =
+        GetNamespaceContext(D->getDeclContext());
     // Global variables.
     if (auto VD = dyn_cast<VarDecl>(D)) {
       for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
@@ -1001,4 +1013,24 @@ void Sema::ProcessAPINotes(Decl *D) {
       return;
     }
   }
+
+  if (auto CXXRecord = dyn_cast<CXXRecordDecl>(D->getDeclContext())) {
+    auto GetRecordContext = [&](api_notes::APINotesReader *Reader)
+        -> std::optional<api_notes::ContextID> {
+      auto ParentContext = GetNamespaceContext(CXXRecord->getDeclContext());
+      if (auto Found = Reader->lookupTagID(CXXRecord->getName(), ParentContext))
+        return *Found;
+
+      return std::nullopt;
+    };
+
+    if (auto CXXMethod = dyn_cast<CXXMethodDecl>(D)) {
+      for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
+        if (auto Context = GetRecordContext(Reader)) {
+          auto Info = Reader->lookupCXXMethod(*Context, CXXMethod->getName());
+          ProcessVersionedAPINotes(*this, CXXMethod, Info);
+        }
+      }
+    }
+  }
 }

diff  --git a/clang/test/APINotes/Inputs/Headers/Methods.apinotes b/clang/test/APINotes/Inputs/Headers/Methods.apinotes
new file mode 100644
index 0000000000000..0fa6991a51ff4
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/Methods.apinotes
@@ -0,0 +1,8 @@
+---
+Name: Methods
+Tags:
+- Name: IntWrapper
+  Methods:
+  - Name: getIncremented
+    Availability: none
+    AvailabilityMsg: "oh no"

diff  --git a/clang/test/APINotes/Inputs/Headers/Methods.h b/clang/test/APINotes/Inputs/Headers/Methods.h
new file mode 100644
index 0000000000000..f46fe31533e5d
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/Methods.h
@@ -0,0 +1,14 @@
+struct IntWrapper {
+  int value;
+
+  IntWrapper getIncremented() const { return {value + 1}; }
+};
+
+// TODO: support nested tags
+struct Outer {
+  struct Inner {
+    int value;
+
+    Inner getDecremented() const { return {value - 1}; }
+  };
+};

diff  --git a/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes b/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
index e9da36787b638..68073932d600e 100644
--- a/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
+++ b/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
@@ -38,6 +38,9 @@ Namespaces:
         Tags:
           - Name: char_box
             SwiftName: NestedCharBox
+            Methods:
+              - Name: methodInNestedNamespace
+                SwiftName: swiftMethodInNestedNamespace()
         Namespaces:
           - Name: Namespace1
             Tags:

diff  --git a/clang/test/APINotes/Inputs/Headers/Namespaces.h b/clang/test/APINotes/Inputs/Headers/Namespaces.h
index 6a79e996be86c..e996b8ffa6b6e 100644
--- a/clang/test/APINotes/Inputs/Headers/Namespaces.h
+++ b/clang/test/APINotes/Inputs/Headers/Namespaces.h
@@ -9,6 +9,7 @@ namespace Nested1 {
 void funcInNestedNamespace(int i);
 struct char_box {
   char c;
+  void methodInNestedNamespace();
 };
 }
 

diff  --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap
index d515169184f4f..faf6042c78d57 100644
--- a/clang/test/APINotes/Inputs/Headers/module.modulemap
+++ b/clang/test/APINotes/Inputs/Headers/module.modulemap
@@ -24,6 +24,10 @@ module BrokenTypes {
   header "BrokenTypes.h"
 }
 
+module Methods {
+  header "Methods.h"
+}
+
 module ModuleWithWrongCase {
   header "ModuleWithWrongCase.h"
 }

diff  --git a/clang/test/APINotes/methods.cpp b/clang/test/APINotes/methods.cpp
new file mode 100644
index 0000000000000..692f750ed66c7
--- /dev/null
+++ b/clang/test/APINotes/methods.cpp
@@ -0,0 +1,9 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Methods -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -x c++
+// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Methods -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter IntWrapper::getIncremented -x c++ | FileCheck --check-prefix=CHECK-METHOD %s
+
+#include "Methods.h"
+
+// CHECK-METHOD: Dumping IntWrapper::getIncremented:
+// CHECK-METHOD-NEXT: CXXMethodDecl {{.+}} getIncremented
+// CHECK-METHOD: UnavailableAttr {{.+}} <<invalid sloc>> "oh no"

diff  --git a/clang/test/APINotes/namespaces.cpp b/clang/test/APINotes/namespaces.cpp
index c19eee565c2da..a6517a324b9c5 100644
--- a/clang/test/APINotes/namespaces.cpp
+++ b/clang/test/APINotes/namespaces.cpp
@@ -9,6 +9,7 @@
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter Namespace1::Nested2::varInNestedNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-ANOTHER-GLOBAL-IN-NESTED-NAMESPACE %s
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter Namespace1::Nested1::char_box -x objective-c++ | FileCheck -check-prefix=CHECK-STRUCT-IN-NESTED-NAMESPACE %s
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter Namespace1::Nested1::funcInNestedNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-FUNC-IN-NESTED-NAMESPACE %s
+// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter Namespace1::Nested1::char_box::methodInNestedNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-METHOD-IN-NESTED-NAMESPACE %s
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter Namespace1::Nested1::Namespace1::char_box -x objective-c++ | FileCheck -check-prefix=CHECK-STRUCT-IN-DEEP-NESTED-NAMESPACE %s
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter varInInlineNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-GLOBAL-IN-INLINE-NAMESPACE %s
 // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/CxxInterop -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter funcInInlineNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-FUNC-IN-INLINE-NAMESPACE %s
@@ -55,6 +56,10 @@
 // CHECK-STRUCT-IN-NESTED-NAMESPACE-NEXT: CXXRecordDecl {{.+}} imported in Namespaces <undeserialized declarations> struct char_box
 // CHECK-STRUCT-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "NestedCharBox"
 
+// CHECK-METHOD-IN-NESTED-NAMESPACE: Dumping Namespace1::Nested1::char_box::methodInNestedNamespace:
+// CHECK-METHOD-IN-NESTED-NAMESPACE-NEXT: CXXMethodDecl {{.+}} imported in Namespaces methodInNestedNamespace
+// CHECK-METHOD-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "swiftMethodInNestedNamespace()"
+
 // CHECK-STRUCT-IN-DEEP-NESTED-NAMESPACE: Dumping Namespace1::Nested1::Namespace1::char_box:
 // CHECK-STRUCT-IN-DEEP-NESTED-NAMESPACE-NEXT: CXXRecordDecl {{.+}} imported in Namespaces <undeserialized declarations> struct char_box
 // CHECK-STRUCT-IN-DEEP-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "DeepNestedCharBox"


        


More information about the cfe-commits mailing list