Skip to content

[clang][ExtractAPI] Compute inherited availability information #103040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading