[clang] [APINotes] Support nested tags (PR #99655)
Egor Zhdan via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 22 10:02:18 PDT 2024
https://github.com/egorzhdan updated https://github.com/llvm/llvm-project/pull/99655
>From f1e3a236ca5dbc64913b0d44bdf92a30d38b8277 Mon Sep 17 00:00:00 2001
From: Egor Zhdan <e_zhdan at apple.com>
Date: Fri, 19 Jul 2024 15:24:10 +0100
Subject: [PATCH] [APINotes] Support nested tags
This allows annotating C/C++ structs declared within other structs using API Notes.
rdar://132083354
---
clang/lib/APINotes/APINotesFormat.h | 2 +-
clang/lib/APINotes/APINotesYAMLCompiler.cpp | 15 ++-
clang/lib/Sema/SemaAPINotes.cpp | 119 +++++++++++-------
.../APINotes/Inputs/Headers/Methods.apinotes | 24 ++++
clang/test/APINotes/Inputs/Headers/Methods.h | 1 -
.../Inputs/Headers/Namespaces.apinotes | 3 +
.../test/APINotes/Inputs/Headers/Namespaces.h | 3 +
clang/test/APINotes/methods.cpp | 8 ++
clang/test/APINotes/namespaces.cpp | 5 +
9 files changed, 131 insertions(+), 49 deletions(-)
diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index cd6456dbe37b2..9d254dcc1c9ef 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 = 27; // SingleDeclTableKey
+const uint16_t VERSION_MINOR = 28; // nested tags
const uint8_t kSwiftCopyable = 1;
const uint8_t kSwiftNonCopyable = 2;
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 060e1fdaf2fd9..11cccc94a15f0 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -406,6 +406,9 @@ template <> struct ScalarEnumerationTraits<EnumConvenienceAliasKind> {
} // namespace llvm
namespace {
+struct Tag;
+typedef std::vector<Tag> TagsSeq;
+
struct Tag {
StringRef Name;
AvailabilityItem Availability;
@@ -421,9 +424,11 @@ struct Tag {
std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
std::optional<bool> SwiftCopyable;
FunctionsSeq Methods;
-};
-typedef std::vector<Tag> TagsSeq;
+ /// Tags that are declared within the current tag. Only the tags that have
+ /// corresponding API Notes will be listed.
+ TagsSeq Tags;
+};
} // namespace
LLVM_YAML_IS_SEQUENCE_VECTOR(Tag)
@@ -456,6 +461,7 @@ template <> struct MappingTraits<Tag> {
IO.mapOptional("EnumKind", T.EnumConvenienceKind);
IO.mapOptional("SwiftCopyable", T.SwiftCopyable);
IO.mapOptional("Methods", T.Methods);
+ IO.mapOptional("Tags", T.Tags);
}
};
} // namespace yaml
@@ -958,12 +964,17 @@ class YAMLConverter {
ContextInfo CI;
auto TagCtxID = Writer.addContext(ParentContextID, T.Name, ContextKind::Tag,
CI, SwiftVersion);
+ Context TagCtx(TagCtxID, ContextKind::Tag);
for (const auto &CXXMethod : T.Methods) {
CXXMethodInfo MI;
convertFunction(CXXMethod, MI);
Writer.addCXXMethod(TagCtxID, CXXMethod.Name, MI, SwiftVersion);
}
+
+ // Convert nested tags.
+ for (const auto &Tag : T.Tags)
+ convertTagContext(TagCtx, Tag, SwiftVersion);
}
void convertTopLevelItems(std::optional<Context> Ctx,
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 055e66a0c3486..be5b7b92dfe6f 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -783,51 +783,77 @@ static void ProcessVersionedAPINotes(
}
}
+static std::optional<api_notes::Context>
+UnwindNamespaceContext(DeclContext *DC, api_notes::APINotesManager &APINotes) {
+ 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.
+ std::stack<NamespaceDecl *> NamespaceStack;
+ {
+ for (auto CurrentNamespace = NamespaceContext; CurrentNamespace;
+ CurrentNamespace =
+ dyn_cast<NamespaceDecl>(CurrentNamespace->getParent())) {
+ if (!CurrentNamespace->isInlineNamespace())
+ NamespaceStack.push(CurrentNamespace);
+ }
+ }
+ std::optional<api_notes::ContextID> NamespaceID;
+ while (!NamespaceStack.empty()) {
+ auto CurrentNamespace = NamespaceStack.top();
+ NamespaceStack.pop();
+ NamespaceID =
+ Reader->lookupNamespaceID(CurrentNamespace->getName(), NamespaceID);
+ if (!NamespaceID)
+ return std::nullopt;
+ }
+ if (NamespaceID)
+ return api_notes::Context(*NamespaceID,
+ api_notes::ContextKind::Namespace);
+ }
+ }
+ return std::nullopt;
+}
+
+static std::optional<api_notes::Context>
+UnwindTagContext(TagDecl *DC, api_notes::APINotesManager &APINotes) {
+ assert(DC && "tag context must not be null");
+ for (auto Reader : APINotes.findAPINotes(DC->getLocation())) {
+ // Retrieve the context ID for the parent tag of the decl.
+ std::stack<TagDecl *> TagStack;
+ {
+ for (auto CurrentTag = DC; CurrentTag;
+ CurrentTag = dyn_cast<TagDecl>(CurrentTag->getParent()))
+ TagStack.push(CurrentTag);
+ }
+ assert(!TagStack.empty());
+ std::optional<api_notes::Context> Ctx =
+ UnwindNamespaceContext(TagStack.top()->getDeclContext(), APINotes);
+ while (!TagStack.empty()) {
+ auto CurrentTag = TagStack.top();
+ TagStack.pop();
+ auto CtxID = Reader->lookupTagID(CurrentTag->getName(), Ctx);
+ if (!CtxID)
+ return std::nullopt;
+ Ctx = api_notes::Context(*CtxID, api_notes::ContextKind::Tag);
+ }
+ return Ctx;
+ }
+ return std::nullopt;
+}
+
/// Process API notes that are associated with this declaration, mapping them
/// to attributes as appropriate.
void Sema::ProcessAPINotes(Decl *D) {
if (!D)
return;
- 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.
- std::stack<NamespaceDecl *> NamespaceStack;
- {
- for (auto CurrentNamespace = NamespaceContext; CurrentNamespace;
- CurrentNamespace =
- dyn_cast<NamespaceDecl>(CurrentNamespace->getParent())) {
- if (!CurrentNamespace->isInlineNamespace())
- NamespaceStack.push(CurrentNamespace);
- }
- }
- std::optional<api_notes::ContextID> NamespaceID;
- while (!NamespaceStack.empty()) {
- auto CurrentNamespace = NamespaceStack.top();
- NamespaceStack.pop();
- NamespaceID = Reader->lookupNamespaceID(CurrentNamespace->getName(),
- NamespaceID);
- if (!NamespaceID)
- break;
- }
- if (NamespaceID)
- 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());
+ UnwindNamespaceContext(D->getDeclContext(), APINotes);
// Global variables.
if (auto VD = dyn_cast<VarDecl>(D)) {
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
@@ -899,6 +925,8 @@ void Sema::ProcessAPINotes(Decl *D) {
}
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
+ if (auto ParentTag = dyn_cast<TagDecl>(Tag->getDeclContext()))
+ APINotesContext = UnwindTagContext(ParentTag, APINotes);
auto Info = Reader->lookupTag(LookupName, APINotesContext);
ProcessVersionedAPINotes(*this, Tag, Info);
}
@@ -1014,23 +1042,24 @@ void Sema::ProcessAPINotes(Decl *D) {
}
}
- 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 TagContext = dyn_cast<TagDecl>(D->getDeclContext())) {
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());
+ if (auto Context = UnwindTagContext(TagContext, APINotes)) {
+ auto Info =
+ Reader->lookupCXXMethod(Context->id, CXXMethod->getName());
ProcessVersionedAPINotes(*this, CXXMethod, Info);
}
}
}
+
+ if (auto Tag = dyn_cast<TagDecl>(D)) {
+ for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
+ if (auto Context = UnwindTagContext(TagContext, APINotes)) {
+ auto Info = Reader->lookupTag(Tag->getName(), Context);
+ ProcessVersionedAPINotes(*this, Tag, Info);
+ }
+ }
+ }
}
}
diff --git a/clang/test/APINotes/Inputs/Headers/Methods.apinotes b/clang/test/APINotes/Inputs/Headers/Methods.apinotes
index 0fa6991a51ff4..618c2cb14b47f 100644
--- a/clang/test/APINotes/Inputs/Headers/Methods.apinotes
+++ b/clang/test/APINotes/Inputs/Headers/Methods.apinotes
@@ -6,3 +6,27 @@ Tags:
- Name: getIncremented
Availability: none
AvailabilityMsg: "oh no"
+ - Name: getDecremented
+ Availability: none
+ AvailabilityMsg: "this should have no effect"
+- Name: Outer
+ Tags:
+ - Name: Inner
+ Methods:
+ - Name: getDecremented
+ Availability: none
+ AvailabilityMsg: "nope"
+ - Name: getIncremented
+ Availability: none
+ AvailabilityMsg: "this should have no effect"
+ Methods:
+ - Name: getDecremented
+ Availability: none
+ AvailabilityMsg: "this should have no effect"
+Functions:
+- Name: getIncremented
+ Availability: none
+ AvailabilityMsg: "this should have no effect"
+- Name: getDecremented
+ Availability: none
+ AvailabilityMsg: "this should have no effect"
diff --git a/clang/test/APINotes/Inputs/Headers/Methods.h b/clang/test/APINotes/Inputs/Headers/Methods.h
index f46fe31533e5d..6a96b12762871 100644
--- a/clang/test/APINotes/Inputs/Headers/Methods.h
+++ b/clang/test/APINotes/Inputs/Headers/Methods.h
@@ -4,7 +4,6 @@ struct IntWrapper {
IntWrapper getIncremented() const { return {value + 1}; }
};
-// TODO: support nested tags
struct Outer {
struct Inner {
int value;
diff --git a/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes b/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
index 68073932d600e..b7d962e0efda6 100644
--- a/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
+++ b/clang/test/APINotes/Inputs/Headers/Namespaces.apinotes
@@ -41,6 +41,9 @@ Namespaces:
Methods:
- Name: methodInNestedNamespace
SwiftName: swiftMethodInNestedNamespace()
+ Tags:
+ - Name: inner_char_box
+ SwiftName: InnerCharBox
Namespaces:
- Name: Namespace1
Tags:
diff --git a/clang/test/APINotes/Inputs/Headers/Namespaces.h b/clang/test/APINotes/Inputs/Headers/Namespaces.h
index e996b8ffa6b6e..c7a891f205c06 100644
--- a/clang/test/APINotes/Inputs/Headers/Namespaces.h
+++ b/clang/test/APINotes/Inputs/Headers/Namespaces.h
@@ -10,6 +10,9 @@ void funcInNestedNamespace(int i);
struct char_box {
char c;
void methodInNestedNamespace();
+ struct inner_char_box {
+ char c;
+ };
};
}
diff --git a/clang/test/APINotes/methods.cpp b/clang/test/APINotes/methods.cpp
index 692f750ed66c7..910565745bea2 100644
--- a/clang/test/APINotes/methods.cpp
+++ b/clang/test/APINotes/methods.cpp
@@ -1,9 +1,17 @@
// 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
+// 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 Outer::Inner::getDecremented -x c++ | FileCheck --check-prefix=CHECK-DEEP-METHOD %s
#include "Methods.h"
// CHECK-METHOD: Dumping IntWrapper::getIncremented:
// CHECK-METHOD-NEXT: CXXMethodDecl {{.+}} getIncremented
// CHECK-METHOD: UnavailableAttr {{.+}} <<invalid sloc>> "oh no"
+
+// CHECK-DEEP-METHOD: Dumping Outer::Inner::getDecremented:
+// CHECK-DEEP-METHOD-NEXT: CXXMethodDecl {{.+}} getDecremented
+// CHECK-DEEP-METHOD: UnavailableAttr {{.+}} <<invalid sloc>> "nope"
+
+// CHECK-METHOD-NOT: this should have no effect
+// CHECK-DEEP-METHOD-NOT: this should have no effect
diff --git a/clang/test/APINotes/namespaces.cpp b/clang/test/APINotes/namespaces.cpp
index a6517a324b9c5..df108b02e6301 100644
--- a/clang/test/APINotes/namespaces.cpp
+++ b/clang/test/APINotes/namespaces.cpp
@@ -8,6 +8,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::Nested1::varInNestedNamespace -x objective-c++ | FileCheck -check-prefix=CHECK-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::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::char_box::inner_char_box -x objective-c++ | FileCheck -check-prefix=CHECK-STRUCT-IN-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
@@ -56,6 +57,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-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE: Dumping Namespace1::Nested1::char_box::inner_char_box:
+// CHECK-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE-NEXT: CXXRecordDecl {{.+}} imported in Namespaces <undeserialized declarations> struct inner_char_box
+// CHECK-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "InnerCharBox"
+
// 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()"
More information about the cfe-commits
mailing list