[clang] [APINotes] Support fields of C/C++ structs (PR #104088)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 14 10:45:59 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Egor Zhdan (egorzhdan)
<details>
<summary>Changes</summary>
This allows annotating fields of C/C++ structs using API Notes.
Previously API Notes supported Objective-C properties, but not fields.
rdar://131548377
---
Patch is 22.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/104088.diff
12 Files Affected:
- (modified) clang/include/clang/APINotes/APINotesReader.h (+7)
- (modified) clang/include/clang/APINotes/APINotesWriter.h (+8)
- (modified) clang/include/clang/APINotes/Types.h (+6)
- (modified) clang/lib/APINotes/APINotesFormat.h (+19-1)
- (modified) clang/lib/APINotes/APINotesReader.cpp (+130)
- (modified) clang/lib/APINotes/APINotesWriter.cpp (+73)
- (modified) clang/lib/APINotes/APINotesYAMLCompiler.cpp (+52-12)
- (modified) clang/lib/Sema/SemaAPINotes.cpp (+20)
- (added) clang/test/APINotes/Inputs/Headers/Fields.apinotes (+23)
- (added) clang/test/APINotes/Inputs/Headers/Fields.h (+21)
- (modified) clang/test/APINotes/Inputs/Headers/module.modulemap (+5)
- (added) clang/test/APINotes/fields.cpp (+17)
``````````diff
diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 03657352c49a59..baf63340640242 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -141,6 +141,13 @@ class APINotesReader {
ObjCSelectorRef Selector,
bool IsInstanceMethod);
+ /// Look for information regarding the given field of a C struct.
+ ///
+ /// \param Name The name of the field.
+ ///
+ /// \returns information about the field, if known.
+ VersionedInfo<FieldInfo> lookupField(ContextID CtxID, llvm::StringRef Name);
+
/// Look for information regarding the given C++ method in the given C++ tag
/// context.
///
diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h
index e0fe5eacef7258..3cc16c3d959faf 100644
--- a/clang/include/clang/APINotes/APINotesWriter.h
+++ b/clang/include/clang/APINotes/APINotesWriter.h
@@ -86,6 +86,14 @@ class APINotesWriter {
void addCXXMethod(ContextID CtxID, llvm::StringRef Name,
const CXXMethodInfo &Info, llvm::VersionTuple SwiftVersion);
+ /// Add information about a specific C record field.
+ ///
+ /// \param CtxID The context in which this field resides, i.e. a C/C++ tag.
+ /// \param Name The name of the field.
+ /// \param Info Information about this field.
+ void addField(ContextID CtxID, llvm::StringRef Name, const FieldInfo &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 f972d0cf26640d..89889910d1a073 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/C++ record field.
+class FieldInfo : public VariableInfo {
+public:
+ FieldInfo() {}
+};
+
/// Describes API notes data for a C++ method.
class CXXMethodInfo : public FunctionInfo {
public:
diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index fba5f4e8907dae..d724a9ea471b54 100644
--- a/clang/lib/APINotes/APINotesFormat.h
+++ b/clang/lib/APINotes/APINotesFormat.h
@@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0;
/// API notes file minor version number.
///
/// When the format changes IN ANY WAY, this number should be incremented.
-const uint16_t VERSION_MINOR = 29; // SwiftConformsTo
+const uint16_t VERSION_MINOR = 30; // fields
const uint8_t kSwiftCopyable = 1;
const uint8_t kSwiftNonCopyable = 2;
@@ -72,6 +72,10 @@ enum BlockID {
/// used in other tables.
OBJC_SELECTOR_BLOCK_ID,
+ /// The fields data block, which maps names fields of C records to
+ /// information about the field.
+ FIELD_BLOCK_ID,
+
/// The global variables data block, which maps global variable names to
/// information about the global variable.
GLOBAL_VARIABLE_BLOCK_ID,
@@ -199,6 +203,20 @@ using CXXMethodDataLayout =
>;
} // namespace cxx_method_block
+namespace field_block {
+enum {
+ FIELD_DATA = 1,
+};
+
+using FieldDataLayout =
+ llvm::BCRecordLayout<FIELD_DATA, // record ID
+ llvm::BCVBR<16>, // table offset within the blob (see
+ // below)
+ llvm::BCBlob // map from C (context id, name)
+ // tuples to C field information
+ >;
+} // namespace field_block
+
namespace objc_selector_block {
enum {
OBJC_SELECTOR_DATA = 1,
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index c05fdffe4a071b..a79156ccf732c2 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -299,6 +299,28 @@ class ObjCPropertyTableInfo
}
};
+/// Used to deserialize the on-disk C record field table.
+class FieldTableInfo
+ : public VersionedTableInfo<FieldTableInfo, SingleDeclTableKey, FieldInfo> {
+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 FieldInfo readUnversioned(internal_key_type Key,
+ const uint8_t *&Data) {
+ FieldInfo Info;
+ ReadVariableInfo(Data, Info);
+ return Info;
+ }
+};
+
/// Read serialized ParamInfo.
void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) {
ReadVariableInfo(Data, Info);
@@ -653,6 +675,12 @@ class APINotesReader::Implementation {
/// The Objective-C property table.
std::unique_ptr<SerializedObjCPropertyTable> ObjCPropertyTable;
+ using SerializedFieldTable =
+ llvm::OnDiskIterableChainedHashTable<FieldTableInfo>;
+
+ /// The C record field table.
+ std::unique_ptr<SerializedFieldTable> FieldTable;
+
using SerializedObjCMethodTable =
llvm::OnDiskIterableChainedHashTable<ObjCMethodTableInfo>;
@@ -720,6 +748,8 @@ class APINotesReader::Implementation {
llvm::SmallVectorImpl<uint64_t> &Scratch);
bool readCXXMethodBlock(llvm::BitstreamCursor &Cursor,
llvm::SmallVectorImpl<uint64_t> &Scratch);
+ bool readFieldBlock(llvm::BitstreamCursor &Cursor,
+ llvm::SmallVectorImpl<uint64_t> &Scratch);
bool readObjCSelectorBlock(llvm::BitstreamCursor &Cursor,
llvm::SmallVectorImpl<uint64_t> &Scratch);
bool readGlobalVariableBlock(llvm::BitstreamCursor &Cursor,
@@ -1252,6 +1282,81 @@ bool APINotesReader::Implementation::readCXXMethodBlock(
return false;
}
+bool APINotesReader::Implementation::readFieldBlock(
+ llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) {
+ if (Cursor.EnterSubBlock(FIELD_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 field_block::FIELD_DATA: {
+ // Already saw C++ method table.
+ if (FieldTable)
+ return true;
+
+ uint32_t tableOffset;
+ field_block::FieldDataLayout::readRecord(Scratch, tableOffset);
+ auto base = reinterpret_cast<const uint8_t *>(BlobData.data());
+
+ FieldTable.reset(SerializedFieldTable::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))
@@ -1812,6 +1917,14 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *InputBuffer,
}
break;
+ case FIELD_BLOCK_ID:
+ if (!HasValidControlBlock ||
+ Implementation->readFieldBlock(Cursor, Scratch)) {
+ Failed = true;
+ return;
+ }
+ break;
+
case OBJC_SELECTOR_BLOCK_ID:
if (!HasValidControlBlock ||
Implementation->readObjCSelectorBlock(Cursor, Scratch)) {
@@ -2031,6 +2144,23 @@ auto APINotesReader::lookupObjCMethod(ContextID CtxID, ObjCSelectorRef Selector,
return {Implementation->SwiftVersion, *Known};
}
+auto APINotesReader::lookupField(ContextID CtxID, llvm::StringRef Name)
+ -> VersionedInfo<FieldInfo> {
+ if (!Implementation->FieldTable)
+ return std::nullopt;
+
+ std::optional<IdentifierID> NameID = Implementation->getIdentifier(Name);
+ if (!NameID)
+ return std::nullopt;
+
+ auto Known = Implementation->FieldTable->find(
+ SingleDeclTableKey(CtxID.Value, *NameID));
+ if (Known == Implementation->FieldTable->end())
+ return std::nullopt;
+
+ return {Implementation->SwiftVersion, *Known};
+}
+
auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name)
-> VersionedInfo<CXXMethodInfo> {
if (!Implementation->CXXMethodTable)
diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp
index cf3a0bee393eee..fe3e6f53764e89 100644
--- a/clang/lib/APINotes/APINotesWriter.cpp
+++ b/clang/lib/APINotes/APINotesWriter.cpp
@@ -62,6 +62,13 @@ class APINotesWriter::Implementation {
llvm::SmallVector<std::pair<VersionTuple, ObjCPropertyInfo>, 1>>
ObjCProperties;
+ /// Information about C record fields.
+ ///
+ /// Indexed by the context ID and name ID.
+ llvm::DenseMap<SingleDeclTableKey,
+ llvm::SmallVector<std::pair<VersionTuple, FieldInfo>, 1>>
+ Fields;
+
/// Information about Objective-C methods.
///
/// Indexed by the context ID, selector ID, and Boolean (stored as a char)
@@ -158,6 +165,7 @@ class APINotesWriter::Implementation {
void writeObjCPropertyBlock(llvm::BitstreamWriter &Stream);
void writeObjCMethodBlock(llvm::BitstreamWriter &Stream);
void writeCXXMethodBlock(llvm::BitstreamWriter &Stream);
+ void writeFieldBlock(llvm::BitstreamWriter &Stream);
void writeObjCSelectorBlock(llvm::BitstreamWriter &Stream);
void writeGlobalVariableBlock(llvm::BitstreamWriter &Stream);
void writeGlobalFunctionBlock(llvm::BitstreamWriter &Stream);
@@ -190,6 +198,7 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &OS) {
writeObjCPropertyBlock(Stream);
writeObjCMethodBlock(Stream);
writeCXXMethodBlock(Stream);
+ writeFieldBlock(Stream);
writeObjCSelectorBlock(Stream);
writeGlobalVariableBlock(Stream);
writeGlobalFunctionBlock(Stream);
@@ -858,6 +867,62 @@ void APINotesWriter::Implementation::writeCXXMethodBlock(
}
}
+namespace {
+/// Used to serialize the on-disk C field table.
+class FieldTableInfo
+ : public VersionedTableInfo<FieldTableInfo, SingleDeclTableKey, FieldInfo> {
+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 FieldInfo &FI) {
+ return getVariableInfoSize(FI);
+ }
+
+ void emitUnversionedInfo(raw_ostream &OS, const FieldInfo &FI) {
+ emitVariableInfo(OS, FI);
+ }
+};
+} // namespace
+
+void APINotesWriter::Implementation::writeFieldBlock(
+ llvm::BitstreamWriter &Stream) {
+ llvm::BCBlockRAII Scope(Stream, FIELD_BLOCK_ID, 3);
+
+ if (Fields.empty())
+ return;
+
+ {
+ llvm::SmallString<4096> HashTableBlob;
+ uint32_t Offset;
+ {
+ llvm::OnDiskChainedHashTableGenerator<FieldTableInfo> Generator;
+ for (auto &MD : Fields)
+ 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);
+ }
+
+ field_block::FieldDataLayout FieldData(Stream);
+ FieldData.emit(Scratch, Offset, HashTableBlob);
+ }
+}
+
namespace {
/// Used to serialize the on-disk Objective-C selector table.
class ObjCSelectorTableInfo {
@@ -1423,6 +1488,14 @@ void APINotesWriter::addCXXMethod(ContextID CtxID, llvm::StringRef Name,
Implementation->CXXMethods[Key].push_back({SwiftVersion, Info});
}
+void APINotesWriter::addField(ContextID CtxID, llvm::StringRef Name,
+ const FieldInfo &Info,
+ VersionTuple SwiftVersion) {
+ IdentifierID NameID = Implementation->getIdentifier(Name);
+ SingleDeclTableKey Key(CtxID.Value, NameID);
+ Implementation->Fields[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 2205686c4d15c3..16fd59244086fd 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -405,6 +405,38 @@ template <> struct ScalarEnumerationTraits<EnumConvenienceAliasKind> {
} // namespace yaml
} // namespace llvm
+namespace {
+struct Field {
+ StringRef Name;
+ std::optional<NullabilityKind> Nullability;
+ AvailabilityItem Availability;
+ std::optional<bool> SwiftPrivate;
+ StringRef SwiftName;
+ StringRef Type;
+};
+
+typedef std::vector<Field> FieldsSeq;
+} // namespace
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(Field)
+
+namespace llvm {
+namespace yaml {
+template <> struct MappingTraits<Field> {
+ static void mapping(IO &IO, Field &F) {
+ IO.mapRequired("Name", F.Name);
+ IO.mapOptional("Nullability", F.Nullability, std::nullopt);
+ IO.mapOptional("Availability", F.Availability.Mode,
+ APIAvailability::Available);
+ IO.mapOptional("AvailabilityMsg", F.Availability.Msg, StringRef(""));
+ IO.mapOptional("SwiftPrivate", F.SwiftPrivate);
+ IO.mapOptional("SwiftName", F.SwiftName, StringRef(""));
+ IO.mapOptional("Type", F.Type, StringRef(""));
+ }
+};
+} // namespace yaml
+} // namespace llvm
+
namespace {
struct Tag;
typedef std::vector<Tag> TagsSeq;
@@ -425,6 +457,7 @@ struct Tag {
std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
std::optional<bool> SwiftCopyable;
FunctionsSeq Methods;
+ FieldsSeq Fields;
/// Tags that are declared within the current tag. Only the tags that have
/// corresponding API Notes will be listed.
@@ -463,6 +496,7 @@ template <> struct MappingTraits<Tag> {
IO.mapOptional("EnumKind", T.EnumConvenienceKind);
IO.mapOptional("SwiftCopyable", T.SwiftCopyable);
IO.mapOptional("Methods", T.Methods);
+ IO.mapOptional("Fields", T.Fields);
IO.mapOptional("Tags", T.Tags);
}
};
@@ -793,6 +827,16 @@ class YAMLConverter {
SwiftVersion);
}
+ template <typename T>
+ void convertVariable(const T &Entity, VariableInfo &VI) {
+ convertAvailability(Entity.Availability, VI, Entity.Name);
+ VI.setSwiftPrivate(Entity.SwiftPrivate);
+ VI.SwiftName = std::string(Entity.SwiftName);
+ if (Entity.Nullability)
+ VI.setNullabilityAudited(*Entity.Nullability);
+ VI.setType(std::string(Entity.Type));
+ }
+
void convertContext(std::optional<ContextID> ParentContextID, const Class &C,
ContextKind Kind, VersionTuple SwiftVersion) {
// Write the class.
@@ -848,14 +892,9 @@ class YAMLConverter {
// Translate from Property into ObjCPropertyInfo.
ObjCPropertyInfo PI;
- convertAvailability(Property.Availability, PI, Property.Name);
- PI.setSwiftPrivate(Property.SwiftPrivate);
- PI.SwiftName = std::string(Property.SwiftName);
- if (Property.Nullability)
- PI.setNullabilityAudited(*Property.Nullability);
+ convertVariable(Property, PI);
if (Property.SwiftImportAsAccessors)
PI.setSwiftImportAsAccessors(*Property.SwiftImportAsAccessors);
- PI.setType(std::string(Property.Type));
// Add both instance and class properties with this name.
if (Property.Kind) {
@@ -970,6 +1009,12 @@ class YAMLConverter {
CI, SwiftVersion);
Context TagCtx(TagCtxID, ContextKind::Tag);
+ for (const auto &Field : T.Fields) {
+ FieldInfo FI;
+ convertVariable(Field, FI);
+ Writer.addField(TagCtxID, Field.Name, FI, SwiftVersion);
+ }
+
for (const auto &CXXMethod : T.Methods) {
CXXMethodInfo MI;
convertFunction(CXXMethod, MI);
@@ -1037,12 +1082,7 @@ class YAMLConverter {
}
GlobalVariableInfo GVI;
- convertAvailability(Global.Availability, GVI, Global.Name);
- GVI.setSwiftPrivate(Global.SwiftPrivate);
- GVI.SwiftName = std::string(Global.SwiftName);
- if (Global.Nullability)
- GVI.setNullabilityAudited(*Global.Nullability);
- GVI.setType(std::string(Global.Type));
+ convertVariable(Global, GVI);
Writer.addGlobalVariable(Ctx, Global.Name, GVI, SwiftVersion);
}
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 65b56bd1c8efc7..d0236d08c98e68 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -433,6 +433,15 @@ static void ProcessAPINotes(Sema &S, VarDecl *D,
metadata);
}
+/// Process API notes for a C field.
+static void ProcessAPINotes(Sema &S, FieldDecl *D,
+ const api_notes::FieldInfo &Info,
+ VersionedInfoMetadata metadata) {
+ // Handle common entity information.
+ ProcessAPINotes(S, D, static_cast<const api_notes::VariableInfo &>(Info),
+ metadata);
+}
+
/// Process API notes for an Objective-C property.
static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D,
const api_notes::ObjCPropertyInfo &Info,
@@ -1062,6 +1071,17 @@ void Sema::ProcessAPINotes(Decl *D) {
}
}
+ if (auto Field = dyn_cast<FieldDecl>(D)) {
+ if (!Field->isUnnamedBitField() && !Field->isAnonymousStructOrUnion()) {
+ for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
+ if (auto Context = UnwindTagContext(TagContext, APINotes)) {
+ auto Info = Reader->lookupField(Context->id, Field->getName());
+ ProcessVersionedAPINotes(*this, Field, Info);
+ }
+ }
+ }
+ }
+
if (auto Tag = dyn_cast<TagDecl>(D)) {
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
if (auto Context = UnwindTagContext(TagContext, APINotes)) {
diff --git a/clang/test/APINotes/Inputs/Headers/Fields.apinotes b/clang/test/APINotes/Inputs/Headers/Fields.apinotes
new file mode 100644
index 00000000000000..931da52ba29d18
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/Fields.apinotes
@@ -0,0 +1,23 @@
+---
+Name: Fields
+Tags:
+- Name: IntWrapper
+ Fields:
+ - Name: value
+ Availability: none
+ AvailabilityMsg: "oh no"
+- Name: Outer
+ Tags:
+ - Name: Inner
+ Fields:
+ - Name: value
+ Availability: none
+ AvailabilityMsg: "oh no 2"
+ Methods:
+ - Name: value
+ Availability: none
+ Availability...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/104088
More information about the cfe-commits
mailing list