Skip to content

Commit

Permalink
[clang][ExtractAPI] Compute inherited availability information (#103040)
Browse files Browse the repository at this point in the history
Additionally this computes availability information for all platforms
ahead of possibly introducing a flag to enable this behavior.

rdar://123513706
  • Loading branch information
daniel-grumberg authored Aug 15, 2024
1 parent a4525fc commit 026d963
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 30 deletions.
4 changes: 4 additions & 0 deletions clang/include/clang/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
103 changes: 87 additions & 16 deletions clang/lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 17 additions & 14 deletions clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
175 changes: 175 additions & 0 deletions clang/test/ExtractAPI/inherited_availability.m
Original file line number Diff line number Diff line change
@@ -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)))
@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

@interface D
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP
@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: ]
@end

// expected-no-diagnostics

0 comments on commit 026d963

Please sign in to comment.