https://github.com/daniel-grumberg created 
https://github.com/llvm/llvm-project/pull/103040

Additionally this computes availability information for all platforms ahead of 
possibly introducing a flag to enable this behavior.

rdar://123513706

>From cd38c476336ea90e4d080638d028dda203b52ac4 Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumb...@apple.com>
Date: Tue, 13 Aug 2024 11:30:18 +0100
Subject: [PATCH] [clang][ExtractAPI] Compute inherited availability
 information

Additionally this computes availability information for all platforms
ahead of possibly introducing a flag to enable this behavior.

rdar://123513706
---
 clang/include/clang/AST/Availability.h        |   4 +
 clang/lib/AST/Availability.cpp                | 100 ++++++++++--
 .../Serialization/SymbolGraphSerializer.cpp   |  31 ++--
 .../test/ExtractAPI/inherited_availability.m  | 149 ++++++++++++++++++
 4 files changed, 254 insertions(+), 30 deletions(-)
 create mode 100644 clang/test/ExtractAPI/inherited_availability.m

diff --git a/clang/include/clang/AST/Availability.h 
b/clang/include/clang/AST/Availability.h
index 26ae622e5b4496..60ca1383f0a44e 100644
--- a/clang/include/clang/AST/Availability.h
+++ b/clang/include/clang/AST/Availability.h
@@ -97,6 +97,10 @@ struct AvailabilityInfo {
     return UnconditionallyUnavailable;
   }
 
+  /// Augments the existing information with additional constraints provided by
+  /// \c Other.
+  void mergeWith(AvailabilityInfo Other);
+
   AvailabilityInfo(StringRef Domain, VersionTuple I, VersionTuple D,
                    VersionTuple O, bool U, bool UD, bool UU)
       : Domain(Domain), Introduced(I), Deprecated(D), Obsoleted(O),
diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp
index 238359a2dedfcf..376a625b41817a 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -16,33 +16,101 @@
 #include "clang/AST/Decl.h"
 #include "clang/Basic/TargetInfo.h"
 
-namespace clang {
+namespace {
+
+struct AvailabilitySet {
+  llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
+  bool UnconditionallyDeprecated = false;
+  bool UnconditionallyUnavailable = false;
 
-AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) {
-  ASTContext &Context = Decl->getASTContext();
-  StringRef PlatformName = Context.getTargetInfo().getPlatformName();
-  AvailabilityInfo Availability;
+  void insert(clang::AvailabilityInfo &&Availability) {
+    auto *Found = getForPlatform(Availability.Domain);
+    if (Found)
+      Found->mergeWith(std::move(Availability));
+    else
+      Availabilities.emplace_back(std::move(Availability));
+  }
+
+  clang::AvailabilityInfo *getForPlatform(llvm::StringRef Domain) {
+    auto *It = llvm::find_if(Availabilities,
+                             [Domain](const clang::AvailabilityInfo &Info) {
+                               return Domain.compare(Info.Domain) == 0;
+                             });
+    return It == Availabilities.end() ? nullptr : It;
+  }
+};
 
+static void createInfoForDecl(const clang::Decl *Decl,
+                              AvailabilitySet &Availabilities) {
   // Collect availability attributes from all redeclarations.
   for (const auto *RD : Decl->redecls()) {
-    for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
-      if (A->getPlatform()->getName() != PlatformName)
-        continue;
-      Availability = AvailabilityInfo(
+    for (const auto *A : RD->specific_attrs<clang::AvailabilityAttr>()) {
+      Availabilities.insert(clang::AvailabilityInfo(
           A->getPlatform()->getName(), A->getIntroduced(), A->getDeprecated(),
-          A->getObsoleted(), A->getUnavailable(), false, false);
-      break;
+          A->getObsoleted(), A->getUnavailable(), false, false));
     }
 
-    if (const auto *A = RD->getAttr<UnavailableAttr>())
+    if (const auto *A = RD->getAttr<clang::UnavailableAttr>())
       if (!A->isImplicit())
-        Availability.UnconditionallyUnavailable = true;
+        Availabilities.UnconditionallyUnavailable = true;
 
-    if (const auto *A = RD->getAttr<DeprecatedAttr>())
+    if (const auto *A = RD->getAttr<clang::DeprecatedAttr>())
       if (!A->isImplicit())
-        Availability.UnconditionallyDeprecated = true;
+        Availabilities.UnconditionallyDeprecated = true;
+  }
+}
+
+} // namespace
+
+namespace clang {
+
+void AvailabilityInfo::mergeWith(AvailabilityInfo Other) {
+  if (isDefault() && Other.isDefault())
+    return;
+
+  if (Domain.empty())
+    Domain = Other.Domain;
+
+  UnconditionallyUnavailable |= Other.UnconditionallyUnavailable;
+  UnconditionallyDeprecated |= Other.UnconditionallyDeprecated;
+  Unavailable |= Other.Unavailable;
+
+  Introduced = std::max(Introduced, Other.Introduced);
+
+  // Default VersionTuple is 0.0.0 so if both are non default let's pick the
+  // smallest version number, otherwise select the one that is non-zero if 
there
+  // is one.
+  if (!Deprecated.empty() && !Other.Deprecated.empty())
+    Deprecated = std::min(Deprecated, Other.Deprecated);
+  else
+    Deprecated = std::max(Deprecated, Other.Deprecated);
+
+  if (!Obsoleted.empty() && !Other.Obsoleted.empty())
+    Obsoleted = std::min(Obsoleted, Other.Obsoleted);
+  else
+    Obsoleted = std::max(Obsoleted, Other.Obsoleted);
+}
+
+AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *D) {
+  AvailabilitySet Availabilities;
+  createInfoForDecl(D, Availabilities);
+  // Traverse
+  for (const auto *Ctx = llvm::cast_or_null<Decl>(D->getDeclContext()); Ctx;
+       Ctx = llvm::cast_or_null<Decl>(Ctx->getDeclContext()))
+    createInfoForDecl(Ctx, Availabilities);
+
+  if (auto *Avail = Availabilities.getForPlatform(
+          D->getASTContext().getTargetInfo().getPlatformName())) {
+    Avail->UnconditionallyDeprecated = 
Availabilities.UnconditionallyDeprecated;
+    Avail->UnconditionallyUnavailable =
+        Availabilities.UnconditionallyUnavailable;
+    return std::move(*Avail);
   }
-  return Availability;
+
+  AvailabilityInfo Avail;
+  Avail.UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated;
+  Avail.UnconditionallyUnavailable = Availabilities.UnconditionallyUnavailable;
+  return Avail;
 }
 
 } // namespace clang
diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp 
b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
index 6e56ee5b573f66..84ed5467dd2fb9 100644
--- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
+++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
@@ -171,22 +171,25 @@ std::optional<Array> serializeAvailability(const 
AvailabilityInfo &Avail) {
     UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true;
     AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated));
   }
-  Object Availability;
-
-  Availability["domain"] = Avail.Domain;
-
-  if (Avail.isUnavailable()) {
-    Availability["isUnconditionallyUnavailable"] = true;
-  } else {
-    serializeObject(Availability, "introduced",
-                    serializeSemanticVersion(Avail.Introduced));
-    serializeObject(Availability, "deprecated",
-                    serializeSemanticVersion(Avail.Deprecated));
-    serializeObject(Availability, "obsoleted",
-                    serializeSemanticVersion(Avail.Obsoleted));
+
+  if (Avail.Domain.str() != "") {
+    Object Availability;
+    Availability["domain"] = Avail.Domain;
+
+    if (Avail.isUnavailable()) {
+      Availability["isUnconditionallyUnavailable"] = true;
+    } else {
+      serializeObject(Availability, "introduced",
+                      serializeSemanticVersion(Avail.Introduced));
+      serializeObject(Availability, "deprecated",
+                      serializeSemanticVersion(Avail.Deprecated));
+      serializeObject(Availability, "obsoleted",
+                      serializeSemanticVersion(Avail.Obsoleted));
+    }
+
+    AvailabilityArray.emplace_back(std::move(Availability));
   }
 
-  AvailabilityArray.emplace_back(std::move(Availability));
   return AvailabilityArray;
 }
 
diff --git a/clang/test/ExtractAPI/inherited_availability.m 
b/clang/test/ExtractAPI/inherited_availability.m
new file mode 100644
index 00000000000000..6b62e58b022ae5
--- /dev/null
+++ b/clang/test/ExtractAPI/inherited_availability.m
@@ -0,0 +1,149 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -extract-api --pretty-sgf 
--emit-sgf-symbol-labels-for-testing -triple arm64-apple-macosx \
+// RUN:   -x objective-c-header %s -o %t/output.symbols.json -verify
+
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix A
+__attribute__((availability(macos, introduced=9.0, deprecated=12.0, 
obsoleted=20.0)))
+@interface A
+// A-LABEL: "!testLabel": "c:objc(cs)A"
+// A:      "availability": [
+// A-NEXT:   {
+// A-NEXT:     "deprecated": {
+// A-NEXT:       "major": 12,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:     "domain": "macos"
+// A-NEXT:     "introduced": {
+// A-NEXT:       "major": 9,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:     "obsoleted": {
+// A-NEXT:       "major": 20,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:   }
+// A-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CP
+@property(class) int CP;
+// CP-LABEL: "!testLabel": "c:objc(cs)A(cpy)CP"
+// CP:      "availability": [
+// CP-NEXT:   {
+// CP-NEXT:     "deprecated": {
+// CP-NEXT:       "major": 12,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:     "domain": "macos"
+// CP-NEXT:     "introduced": {
+// CP-NEXT:       "major": 9,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:     "obsoleted": {
+// CP-NEXT:       "major": 20,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:   }
+// CP-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix IP
+@property int IP;
+// IP-LABEL: "!testLabel": "c:objc(cs)A(py)IP"
+// IP:      "availability": [
+// IP-NEXT:   {
+// IP-NEXT:     "deprecated": {
+// IP-NEXT:       "major": 12,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:     "domain": "macos"
+// IP-NEXT:     "introduced": {
+// IP-NEXT:       "major": 9,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:     "obsoleted": {
+// IP-NEXT:       "major": 20,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:   }
+// IP-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix MR
+@property int moreRestrictive __attribute__((availability(macos, 
introduced=10.0, deprecated=11.0, obsoleted=19.0)));
+// MR-LABEL: "!testLabel": "c:objc(cs)A(py)moreRestrictive"
+// MR:      "availability": [
+// MR-NEXT:   {
+// MR-NEXT:     "deprecated": {
+// MR-NEXT:       "major": 11,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:     "domain": "macos"
+// MR-NEXT:     "introduced": {
+// MR-NEXT:       "major": 10,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:     "obsoleted": {
+// MR-NEXT:       "major": 19,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:   }
+// MR-NEXT: ]
+
+@end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B
+__attribute__((deprecated("B is deprecated")))
+@interface B
+// B-LABEL: "!testLabel": "c:objc(cs)B"
+// B:      "availability": [
+// B-NEXT:   {
+// B-NEXT:     "domain": "*"
+// B-NEXT:     "isUnconditionallyDeprecated": true
+// B-NEXT:   }
+// B-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BIP
+@property int BIP;
+// BIP-LABEL: "!testLabel": "c:objc(cs)B(py)BIP"
+// BIP:      "availability": [
+// BIP-NEXT:   {
+// BIP-NEXT:     "domain": "*"
+// BIP-NEXT:     "isUnconditionallyDeprecated": true
+// BIP-NEXT:   }
+// BIP-NEXT: ]
+@end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C
+__attribute__((availability(macos, unavailable)))
+@interface C
+// C-LABEL: "!testLabel": "c:objc(cs)C"
+// C:      "availability": [
+// C-NEXT:   {
+// C-NEXT:     "domain": "macos"
+// C-NEXT:     "isUnconditionallyUnavailable": true
+// C-NEXT:   }
+// C-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CIP
+@property int CIP;
+// CIP-LABEL: "!testLabel": "c:objc(cs)C(py)CIP"
+// CIP:      "availability": [
+// CIP-NEXT:   {
+// CIP-NEXT:     "domain": "macos"
+// CIP-NEXT:     "isUnconditionallyUnavailable": true
+// CIP-NEXT:   }
+// CIP-NEXT: ]
+@end
+
+// expected-no-diagnostics

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to