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" _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits