Skip to content

Commit 4d5f81c

Browse files
authored
[APINotes] Support nested tags
This allows annotating C/C++ structs declared within other structs using API Notes. rdar://132083354
1 parent b60fec2 commit 4d5f81c

File tree

9 files changed

+131
-49
lines changed

9 files changed

+131
-49
lines changed

clang/lib/APINotes/APINotesFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0;
2424
/// API notes file minor version number.
2525
///
2626
/// When the format changes IN ANY WAY, this number should be incremented.
27-
const uint16_t VERSION_MINOR = 27; // SingleDeclTableKey
27+
const uint16_t VERSION_MINOR = 28; // nested tags
2828

2929
const uint8_t kSwiftCopyable = 1;
3030
const uint8_t kSwiftNonCopyable = 2;

clang/lib/APINotes/APINotesYAMLCompiler.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ template <> struct ScalarEnumerationTraits<EnumConvenienceAliasKind> {
406406
} // namespace llvm
407407

408408
namespace {
409+
struct Tag;
410+
typedef std::vector<Tag> TagsSeq;
411+
409412
struct Tag {
410413
StringRef Name;
411414
AvailabilityItem Availability;
@@ -421,9 +424,11 @@ struct Tag {
421424
std::optional<EnumConvenienceAliasKind> EnumConvenienceKind;
422425
std::optional<bool> SwiftCopyable;
423426
FunctionsSeq Methods;
424-
};
425427

426-
typedef std::vector<Tag> TagsSeq;
428+
/// Tags that are declared within the current tag. Only the tags that have
429+
/// corresponding API Notes will be listed.
430+
TagsSeq Tags;
431+
};
427432
} // namespace
428433

429434
LLVM_YAML_IS_SEQUENCE_VECTOR(Tag)
@@ -456,6 +461,7 @@ template <> struct MappingTraits<Tag> {
456461
IO.mapOptional("EnumKind", T.EnumConvenienceKind);
457462
IO.mapOptional("SwiftCopyable", T.SwiftCopyable);
458463
IO.mapOptional("Methods", T.Methods);
464+
IO.mapOptional("Tags", T.Tags);
459465
}
460466
};
461467
} // namespace yaml
@@ -958,12 +964,17 @@ class YAMLConverter {
958964
ContextInfo CI;
959965
auto TagCtxID = Writer.addContext(ParentContextID, T.Name, ContextKind::Tag,
960966
CI, SwiftVersion);
967+
Context TagCtx(TagCtxID, ContextKind::Tag);
961968

962969
for (const auto &CXXMethod : T.Methods) {
963970
CXXMethodInfo MI;
964971
convertFunction(CXXMethod, MI);
965972
Writer.addCXXMethod(TagCtxID, CXXMethod.Name, MI, SwiftVersion);
966973
}
974+
975+
// Convert nested tags.
976+
for (const auto &Tag : T.Tags)
977+
convertTagContext(TagCtx, Tag, SwiftVersion);
967978
}
968979

969980
void convertTopLevelItems(std::optional<Context> Ctx,

clang/lib/Sema/SemaAPINotes.cpp

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -783,51 +783,77 @@ static void ProcessVersionedAPINotes(
783783
}
784784
}
785785

786+
static std::optional<api_notes::Context>
787+
UnwindNamespaceContext(DeclContext *DC, api_notes::APINotesManager &APINotes) {
788+
if (auto NamespaceContext = dyn_cast<NamespaceDecl>(DC)) {
789+
for (auto Reader : APINotes.findAPINotes(NamespaceContext->getLocation())) {
790+
// Retrieve the context ID for the parent namespace of the decl.
791+
std::stack<NamespaceDecl *> NamespaceStack;
792+
{
793+
for (auto CurrentNamespace = NamespaceContext; CurrentNamespace;
794+
CurrentNamespace =
795+
dyn_cast<NamespaceDecl>(CurrentNamespace->getParent())) {
796+
if (!CurrentNamespace->isInlineNamespace())
797+
NamespaceStack.push(CurrentNamespace);
798+
}
799+
}
800+
std::optional<api_notes::ContextID> NamespaceID;
801+
while (!NamespaceStack.empty()) {
802+
auto CurrentNamespace = NamespaceStack.top();
803+
NamespaceStack.pop();
804+
NamespaceID =
805+
Reader->lookupNamespaceID(CurrentNamespace->getName(), NamespaceID);
806+
if (!NamespaceID)
807+
return std::nullopt;
808+
}
809+
if (NamespaceID)
810+
return api_notes::Context(*NamespaceID,
811+
api_notes::ContextKind::Namespace);
812+
}
813+
}
814+
return std::nullopt;
815+
}
816+
817+
static std::optional<api_notes::Context>
818+
UnwindTagContext(TagDecl *DC, api_notes::APINotesManager &APINotes) {
819+
assert(DC && "tag context must not be null");
820+
for (auto Reader : APINotes.findAPINotes(DC->getLocation())) {
821+
// Retrieve the context ID for the parent tag of the decl.
822+
std::stack<TagDecl *> TagStack;
823+
{
824+
for (auto CurrentTag = DC; CurrentTag;
825+
CurrentTag = dyn_cast<TagDecl>(CurrentTag->getParent()))
826+
TagStack.push(CurrentTag);
827+
}
828+
assert(!TagStack.empty());
829+
std::optional<api_notes::Context> Ctx =
830+
UnwindNamespaceContext(TagStack.top()->getDeclContext(), APINotes);
831+
while (!TagStack.empty()) {
832+
auto CurrentTag = TagStack.top();
833+
TagStack.pop();
834+
auto CtxID = Reader->lookupTagID(CurrentTag->getName(), Ctx);
835+
if (!CtxID)
836+
return std::nullopt;
837+
Ctx = api_notes::Context(*CtxID, api_notes::ContextKind::Tag);
838+
}
839+
return Ctx;
840+
}
841+
return std::nullopt;
842+
}
843+
786844
/// Process API notes that are associated with this declaration, mapping them
787845
/// to attributes as appropriate.
788846
void Sema::ProcessAPINotes(Decl *D) {
789847
if (!D)
790848
return;
791849

792-
auto GetNamespaceContext =
793-
[&](DeclContext *DC) -> std::optional<api_notes::Context> {
794-
if (auto NamespaceContext = dyn_cast<NamespaceDecl>(DC)) {
795-
for (auto Reader :
796-
APINotes.findAPINotes(NamespaceContext->getLocation())) {
797-
// Retrieve the context ID for the parent namespace of the decl.
798-
std::stack<NamespaceDecl *> NamespaceStack;
799-
{
800-
for (auto CurrentNamespace = NamespaceContext; CurrentNamespace;
801-
CurrentNamespace =
802-
dyn_cast<NamespaceDecl>(CurrentNamespace->getParent())) {
803-
if (!CurrentNamespace->isInlineNamespace())
804-
NamespaceStack.push(CurrentNamespace);
805-
}
806-
}
807-
std::optional<api_notes::ContextID> NamespaceID;
808-
while (!NamespaceStack.empty()) {
809-
auto CurrentNamespace = NamespaceStack.top();
810-
NamespaceStack.pop();
811-
NamespaceID = Reader->lookupNamespaceID(CurrentNamespace->getName(),
812-
NamespaceID);
813-
if (!NamespaceID)
814-
break;
815-
}
816-
if (NamespaceID)
817-
return api_notes::Context(*NamespaceID,
818-
api_notes::ContextKind::Namespace);
819-
}
820-
}
821-
return std::nullopt;
822-
};
823-
824850
// Globals.
825851
if (D->getDeclContext()->isFileContext() ||
826852
D->getDeclContext()->isNamespace() ||
827853
D->getDeclContext()->isExternCContext() ||
828854
D->getDeclContext()->isExternCXXContext()) {
829855
std::optional<api_notes::Context> APINotesContext =
830-
GetNamespaceContext(D->getDeclContext());
856+
UnwindNamespaceContext(D->getDeclContext(), APINotes);
831857
// Global variables.
832858
if (auto VD = dyn_cast<VarDecl>(D)) {
833859
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
@@ -899,6 +925,8 @@ void Sema::ProcessAPINotes(Decl *D) {
899925
}
900926

901927
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
928+
if (auto ParentTag = dyn_cast<TagDecl>(Tag->getDeclContext()))
929+
APINotesContext = UnwindTagContext(ParentTag, APINotes);
902930
auto Info = Reader->lookupTag(LookupName, APINotesContext);
903931
ProcessVersionedAPINotes(*this, Tag, Info);
904932
}
@@ -1014,23 +1042,24 @@ void Sema::ProcessAPINotes(Decl *D) {
10141042
}
10151043
}
10161044

1017-
if (auto CXXRecord = dyn_cast<CXXRecordDecl>(D->getDeclContext())) {
1018-
auto GetRecordContext = [&](api_notes::APINotesReader *Reader)
1019-
-> std::optional<api_notes::ContextID> {
1020-
auto ParentContext = GetNamespaceContext(CXXRecord->getDeclContext());
1021-
if (auto Found = Reader->lookupTagID(CXXRecord->getName(), ParentContext))
1022-
return *Found;
1023-
1024-
return std::nullopt;
1025-
};
1026-
1045+
if (auto TagContext = dyn_cast<TagDecl>(D->getDeclContext())) {
10271046
if (auto CXXMethod = dyn_cast<CXXMethodDecl>(D)) {
10281047
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
1029-
if (auto Context = GetRecordContext(Reader)) {
1030-
auto Info = Reader->lookupCXXMethod(*Context, CXXMethod->getName());
1048+
if (auto Context = UnwindTagContext(TagContext, APINotes)) {
1049+
auto Info =
1050+
Reader->lookupCXXMethod(Context->id, CXXMethod->getName());
10311051
ProcessVersionedAPINotes(*this, CXXMethod, Info);
10321052
}
10331053
}
10341054
}
1055+
1056+
if (auto Tag = dyn_cast<TagDecl>(D)) {
1057+
for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
1058+
if (auto Context = UnwindTagContext(TagContext, APINotes)) {
1059+
auto Info = Reader->lookupTag(Tag->getName(), Context);
1060+
ProcessVersionedAPINotes(*this, Tag, Info);
1061+
}
1062+
}
1063+
}
10351064
}
10361065
}

clang/test/APINotes/Inputs/Headers/Methods.apinotes

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,27 @@ Tags:
66
- Name: getIncremented
77
Availability: none
88
AvailabilityMsg: "oh no"
9+
- Name: getDecremented
10+
Availability: none
11+
AvailabilityMsg: "this should have no effect"
12+
- Name: Outer
13+
Tags:
14+
- Name: Inner
15+
Methods:
16+
- Name: getDecremented
17+
Availability: none
18+
AvailabilityMsg: "nope"
19+
- Name: getIncremented
20+
Availability: none
21+
AvailabilityMsg: "this should have no effect"
22+
Methods:
23+
- Name: getDecremented
24+
Availability: none
25+
AvailabilityMsg: "this should have no effect"
26+
Functions:
27+
- Name: getIncremented
28+
Availability: none
29+
AvailabilityMsg: "this should have no effect"
30+
- Name: getDecremented
31+
Availability: none
32+
AvailabilityMsg: "this should have no effect"

clang/test/APINotes/Inputs/Headers/Methods.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ struct IntWrapper {
44
IntWrapper getIncremented() const { return {value + 1}; }
55
};
66

7-
// TODO: support nested tags
87
struct Outer {
98
struct Inner {
109
int value;

clang/test/APINotes/Inputs/Headers/Namespaces.apinotes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Namespaces:
4141
Methods:
4242
- Name: methodInNestedNamespace
4343
SwiftName: swiftMethodInNestedNamespace()
44+
Tags:
45+
- Name: inner_char_box
46+
SwiftName: InnerCharBox
4447
Namespaces:
4548
- Name: Namespace1
4649
Tags:

clang/test/APINotes/Inputs/Headers/Namespaces.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ void funcInNestedNamespace(int i);
1010
struct char_box {
1111
char c;
1212
void methodInNestedNamespace();
13+
struct inner_char_box {
14+
char c;
15+
};
1316
};
1417
}
1518

clang/test/APINotes/methods.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
// RUN: rm -rf %t && mkdir -p %t
22
// 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++
33
// 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
4+
// 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
45

56
#include "Methods.h"
67

78
// CHECK-METHOD: Dumping IntWrapper::getIncremented:
89
// CHECK-METHOD-NEXT: CXXMethodDecl {{.+}} getIncremented
910
// CHECK-METHOD: UnavailableAttr {{.+}} <<invalid sloc>> "oh no"
11+
12+
// CHECK-DEEP-METHOD: Dumping Outer::Inner::getDecremented:
13+
// CHECK-DEEP-METHOD-NEXT: CXXMethodDecl {{.+}} getDecremented
14+
// CHECK-DEEP-METHOD: UnavailableAttr {{.+}} <<invalid sloc>> "nope"
15+
16+
// CHECK-METHOD-NOT: this should have no effect
17+
// CHECK-DEEP-METHOD-NOT: this should have no effect

clang/test/APINotes/namespaces.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// 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
99
// 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
1010
// 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
11+
// 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
1112
// 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
1213
// 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
1314
// 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 @@
5657
// CHECK-STRUCT-IN-NESTED-NAMESPACE-NEXT: CXXRecordDecl {{.+}} imported in Namespaces <undeserialized declarations> struct char_box
5758
// CHECK-STRUCT-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "NestedCharBox"
5859

60+
// CHECK-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE: Dumping Namespace1::Nested1::char_box::inner_char_box:
61+
// CHECK-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE-NEXT: CXXRecordDecl {{.+}} imported in Namespaces <undeserialized declarations> struct inner_char_box
62+
// CHECK-STRUCT-IN-STRUCT-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "InnerCharBox"
63+
5964
// CHECK-METHOD-IN-NESTED-NAMESPACE: Dumping Namespace1::Nested1::char_box::methodInNestedNamespace:
6065
// CHECK-METHOD-IN-NESTED-NAMESPACE-NEXT: CXXMethodDecl {{.+}} imported in Namespaces methodInNestedNamespace
6166
// CHECK-METHOD-IN-NESTED-NAMESPACE: SwiftNameAttr {{.+}} <<invalid sloc>> "swiftMethodInNestedNamespace()"

0 commit comments

Comments
 (0)