[clang] [APINotes] Support annotating C++ methods (PR #99512)
Egor Zhdan via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 18 08:22:43 PDT 2024
https://github.com/egorzhdan created https://github.com/llvm/llvm-project/pull/99512
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
>From 33ac9376e3c38c4cf4b1b7a0beec93b03e3ce806 Mon Sep 17 00:00:00 2001
From: Egor Zhdan <e_zhdan at apple.com>
Date: Thu, 18 Jul 2024 15:49:24 +0100
Subject: [PATCH] [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
---
clang/include/clang/APINotes/APINotesReader.h | 21 +++
clang/include/clang/APINotes/APINotesWriter.h | 8 +
clang/include/clang/APINotes/Types.h | 7 +
clang/lib/APINotes/APINotesFormat.h | 29 ++++
clang/lib/APINotes/APINotesReader.cpp | 149 ++++++++++++++++
clang/lib/APINotes/APINotesWriter.cpp | 72 ++++++++
clang/lib/APINotes/APINotesYAMLCompiler.cpp | 164 ++++++++++--------
clang/lib/Sema/SemaAPINotes.cpp | 50 +++++-
.../APINotes/Inputs/Headers/Methods.apinotes | 8 +
clang/test/APINotes/Inputs/Headers/Methods.h | 14 ++
.../Inputs/Headers/Namespaces.apinotes | 3 +
.../test/APINotes/Inputs/Headers/Namespaces.h | 1 +
.../APINotes/Inputs/Headers/module.modulemap | 4 +
clang/test/APINotes/methods.cpp | 9 +
clang/test/APINotes/namespaces.cpp | 5 +
15 files changed, 465 insertions(+), 79 deletions(-)
create mode 100644 clang/test/APINotes/Inputs/Headers/Methods.apinotes
create mode 100644 clang/test/APINotes/Inputs/Headers/Methods.h
create mode 100644 clang/test/APINotes/methods.cpp
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..768fdc231bd57 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..e259c761591ba 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 &OM : CXXMethods)
+ Generator.insert(OM.first, OM.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