[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