[clang] [clang][ExtractAPI] Compute inherited availability information (PR #103040)
Daniel Grumberg via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 13 03:41:16 PDT 2024
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 <dgrumberg at 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)))
+ 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
+
+// expected-no-diagnostics
More information about the cfe-commits
mailing list