[clang] 026d963 - [clang][ExtractAPI] Compute inherited availability information (#103040)

via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 15 06:19:52 PDT 2024


Author: Daniel Grumberg
Date: 2024-08-15T14:19:49+01:00
New Revision: 026d963cb004689477d2b5798cbba5ad41c25a70

URL: https://github.com/llvm/llvm-project/commit/026d963cb004689477d2b5798cbba5ad41c25a70
DIFF: https://github.com/llvm/llvm-project/commit/026d963cb004689477d2b5798cbba5ad41c25a70.diff

LOG: [clang][ExtractAPI] Compute inherited availability information (#103040)

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

rdar://123513706

Added: 
    clang/test/ExtractAPI/inherited_availability.m

Modified: 
    clang/include/clang/AST/Availability.h
    clang/lib/AST/Availability.cpp
    clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp

Removed: 
    


################################################################################
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..cf040fc727d11f 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -16,33 +16,104 @@
 #include "clang/AST/Decl.h"
 #include "clang/Basic/TargetInfo.h"
 
-namespace clang {
+namespace {
+
+/// Represents the availability of a symbol across platforms.
+struct AvailabilitySet {
+  bool UnconditionallyDeprecated = false;
+  bool UnconditionallyUnavailable = false;
+
+  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;
+  }
 
-AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) {
-  ASTContext &Context = Decl->getASTContext();
-  StringRef PlatformName = Context.getTargetInfo().getPlatformName();
-  AvailabilityInfo Availability;
+private:
+  llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
+};
 
+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;
   }
-  return Availability;
+}
+
+} // 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;
+  // Walk DeclContexts upwards starting from D to find the combined availability
+  // of the symbol.
+  for (const auto *Ctx = D; 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);
+  }
+
+  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..c24e7fa8e208f0
--- /dev/null
+++ b/clang/test/ExtractAPI/inherited_availability.m
@@ -0,0 +1,175 @@
+// 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)))
+ at 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
+ at 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
+ at 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
+ at 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: ]
+
+ at end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B
+__attribute__((deprecated("B is deprecated")))
+ at 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
+ at 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: ]
+ at end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C
+__attribute__((availability(macos, unavailable)))
+ at 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
+ at 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: ]
+ at end
+
+ at interface D
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP
+ at property int DIP __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0)));
+// DIP-LABEL: "!testLabel": "c:objc(cs)D(py)DIP"
+// DIP:      "availability": [
+// DIP-NEXT:   {
+// DIP-NEXT:     "deprecated": {
+// DIP-NEXT:       "major": 11,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:     "domain": "macos"
+// DIP-NEXT:     "introduced": {
+// DIP-NEXT:       "major": 10,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:     "obsoleted": {
+// DIP-NEXT:       "major": 19,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:   }
+// DIP-NEXT: ]
+ at end
+
+// expected-no-diagnostics


        


More information about the cfe-commits mailing list