Skip to content

Commit 026d963

Browse files
[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
1 parent a4525fc commit 026d963

File tree

4 files changed

+283
-30
lines changed

4 files changed

+283
-30
lines changed

clang/include/clang/AST/Availability.h

+4
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ struct AvailabilityInfo {
9797
return UnconditionallyUnavailable;
9898
}
9999

100+
/// Augments the existing information with additional constraints provided by
101+
/// \c Other.
102+
void mergeWith(AvailabilityInfo Other);
103+
100104
AvailabilityInfo(StringRef Domain, VersionTuple I, VersionTuple D,
101105
VersionTuple O, bool U, bool UD, bool UU)
102106
: Domain(Domain), Introduced(I), Deprecated(D), Obsoleted(O),

clang/lib/AST/Availability.cpp

+87-16
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,104 @@
1616
#include "clang/AST/Decl.h"
1717
#include "clang/Basic/TargetInfo.h"
1818

19-
namespace clang {
19+
namespace {
20+
21+
/// Represents the availability of a symbol across platforms.
22+
struct AvailabilitySet {
23+
bool UnconditionallyDeprecated = false;
24+
bool UnconditionallyUnavailable = false;
25+
26+
void insert(clang::AvailabilityInfo &&Availability) {
27+
auto *Found = getForPlatform(Availability.Domain);
28+
if (Found)
29+
Found->mergeWith(std::move(Availability));
30+
else
31+
Availabilities.emplace_back(std::move(Availability));
32+
}
33+
34+
clang::AvailabilityInfo *getForPlatform(llvm::StringRef Domain) {
35+
auto *It = llvm::find_if(Availabilities,
36+
[Domain](const clang::AvailabilityInfo &Info) {
37+
return Domain.compare(Info.Domain) == 0;
38+
});
39+
return It == Availabilities.end() ? nullptr : It;
40+
}
2041

21-
AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) {
22-
ASTContext &Context = Decl->getASTContext();
23-
StringRef PlatformName = Context.getTargetInfo().getPlatformName();
24-
AvailabilityInfo Availability;
42+
private:
43+
llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
44+
};
2545

46+
static void createInfoForDecl(const clang::Decl *Decl,
47+
AvailabilitySet &Availabilities) {
2648
// Collect availability attributes from all redeclarations.
2749
for (const auto *RD : Decl->redecls()) {
28-
for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
29-
if (A->getPlatform()->getName() != PlatformName)
30-
continue;
31-
Availability = AvailabilityInfo(
50+
for (const auto *A : RD->specific_attrs<clang::AvailabilityAttr>()) {
51+
Availabilities.insert(clang::AvailabilityInfo(
3252
A->getPlatform()->getName(), A->getIntroduced(), A->getDeprecated(),
33-
A->getObsoleted(), A->getUnavailable(), false, false);
34-
break;
53+
A->getObsoleted(), A->getUnavailable(), false, false));
3554
}
3655

37-
if (const auto *A = RD->getAttr<UnavailableAttr>())
56+
if (const auto *A = RD->getAttr<clang::UnavailableAttr>())
3857
if (!A->isImplicit())
39-
Availability.UnconditionallyUnavailable = true;
58+
Availabilities.UnconditionallyUnavailable = true;
4059

41-
if (const auto *A = RD->getAttr<DeprecatedAttr>())
60+
if (const auto *A = RD->getAttr<clang::DeprecatedAttr>())
4261
if (!A->isImplicit())
43-
Availability.UnconditionallyDeprecated = true;
62+
Availabilities.UnconditionallyDeprecated = true;
4463
}
45-
return Availability;
64+
}
65+
66+
} // namespace
67+
68+
namespace clang {
69+
70+
void AvailabilityInfo::mergeWith(AvailabilityInfo Other) {
71+
if (isDefault() && Other.isDefault())
72+
return;
73+
74+
if (Domain.empty())
75+
Domain = Other.Domain;
76+
77+
UnconditionallyUnavailable |= Other.UnconditionallyUnavailable;
78+
UnconditionallyDeprecated |= Other.UnconditionallyDeprecated;
79+
Unavailable |= Other.Unavailable;
80+
81+
Introduced = std::max(Introduced, Other.Introduced);
82+
83+
// Default VersionTuple is 0.0.0 so if both are non default let's pick the
84+
// smallest version number, otherwise select the one that is non-zero if there
85+
// is one.
86+
if (!Deprecated.empty() && !Other.Deprecated.empty())
87+
Deprecated = std::min(Deprecated, Other.Deprecated);
88+
else
89+
Deprecated = std::max(Deprecated, Other.Deprecated);
90+
91+
if (!Obsoleted.empty() && !Other.Obsoleted.empty())
92+
Obsoleted = std::min(Obsoleted, Other.Obsoleted);
93+
else
94+
Obsoleted = std::max(Obsoleted, Other.Obsoleted);
95+
}
96+
97+
AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *D) {
98+
AvailabilitySet Availabilities;
99+
// Walk DeclContexts upwards starting from D to find the combined availability
100+
// of the symbol.
101+
for (const auto *Ctx = D; Ctx;
102+
Ctx = llvm::cast_or_null<Decl>(Ctx->getDeclContext()))
103+
createInfoForDecl(Ctx, Availabilities);
104+
105+
if (auto *Avail = Availabilities.getForPlatform(
106+
D->getASTContext().getTargetInfo().getPlatformName())) {
107+
Avail->UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated;
108+
Avail->UnconditionallyUnavailable =
109+
Availabilities.UnconditionallyUnavailable;
110+
return std::move(*Avail);
111+
}
112+
113+
AvailabilityInfo Avail;
114+
Avail.UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated;
115+
Avail.UnconditionallyUnavailable = Availabilities.UnconditionallyUnavailable;
116+
return Avail;
46117
}
47118

48119
} // namespace clang

clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp

+17-14
Original file line numberDiff line numberDiff line change
@@ -171,22 +171,25 @@ std::optional<Array> serializeAvailability(const AvailabilityInfo &Avail) {
171171
UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true;
172172
AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated));
173173
}
174-
Object Availability;
175-
176-
Availability["domain"] = Avail.Domain;
177-
178-
if (Avail.isUnavailable()) {
179-
Availability["isUnconditionallyUnavailable"] = true;
180-
} else {
181-
serializeObject(Availability, "introduced",
182-
serializeSemanticVersion(Avail.Introduced));
183-
serializeObject(Availability, "deprecated",
184-
serializeSemanticVersion(Avail.Deprecated));
185-
serializeObject(Availability, "obsoleted",
186-
serializeSemanticVersion(Avail.Obsoleted));
174+
175+
if (Avail.Domain.str() != "") {
176+
Object Availability;
177+
Availability["domain"] = Avail.Domain;
178+
179+
if (Avail.isUnavailable()) {
180+
Availability["isUnconditionallyUnavailable"] = true;
181+
} else {
182+
serializeObject(Availability, "introduced",
183+
serializeSemanticVersion(Avail.Introduced));
184+
serializeObject(Availability, "deprecated",
185+
serializeSemanticVersion(Avail.Deprecated));
186+
serializeObject(Availability, "obsoleted",
187+
serializeSemanticVersion(Avail.Obsoleted));
188+
}
189+
190+
AvailabilityArray.emplace_back(std::move(Availability));
187191
}
188192

189-
AvailabilityArray.emplace_back(std::move(Availability));
190193
return AvailabilityArray;
191194
}
192195

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// RUN: rm -rf %t
2+
// RUN: %clang_cc1 -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing -triple arm64-apple-macosx \
3+
// RUN: -x objective-c-header %s -o %t/output.symbols.json -verify
4+
5+
6+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix A
7+
__attribute__((availability(macos, introduced=9.0, deprecated=12.0, obsoleted=20.0)))
8+
@interface A
9+
// A-LABEL: "!testLabel": "c:objc(cs)A"
10+
// A: "availability": [
11+
// A-NEXT: {
12+
// A-NEXT: "deprecated": {
13+
// A-NEXT: "major": 12,
14+
// A-NEXT: "minor": 0,
15+
// A-NEXT: "patch": 0
16+
// A-NEXT: }
17+
// A-NEXT: "domain": "macos"
18+
// A-NEXT: "introduced": {
19+
// A-NEXT: "major": 9,
20+
// A-NEXT: "minor": 0,
21+
// A-NEXT: "patch": 0
22+
// A-NEXT: }
23+
// A-NEXT: "obsoleted": {
24+
// A-NEXT: "major": 20,
25+
// A-NEXT: "minor": 0,
26+
// A-NEXT: "patch": 0
27+
// A-NEXT: }
28+
// A-NEXT: }
29+
// A-NEXT: ]
30+
31+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CP
32+
@property(class) int CP;
33+
// CP-LABEL: "!testLabel": "c:objc(cs)A(cpy)CP"
34+
// CP: "availability": [
35+
// CP-NEXT: {
36+
// CP-NEXT: "deprecated": {
37+
// CP-NEXT: "major": 12,
38+
// CP-NEXT: "minor": 0,
39+
// CP-NEXT: "patch": 0
40+
// CP-NEXT: }
41+
// CP-NEXT: "domain": "macos"
42+
// CP-NEXT: "introduced": {
43+
// CP-NEXT: "major": 9,
44+
// CP-NEXT: "minor": 0,
45+
// CP-NEXT: "patch": 0
46+
// CP-NEXT: }
47+
// CP-NEXT: "obsoleted": {
48+
// CP-NEXT: "major": 20,
49+
// CP-NEXT: "minor": 0,
50+
// CP-NEXT: "patch": 0
51+
// CP-NEXT: }
52+
// CP-NEXT: }
53+
// CP-NEXT: ]
54+
55+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix IP
56+
@property int IP;
57+
// IP-LABEL: "!testLabel": "c:objc(cs)A(py)IP"
58+
// IP: "availability": [
59+
// IP-NEXT: {
60+
// IP-NEXT: "deprecated": {
61+
// IP-NEXT: "major": 12,
62+
// IP-NEXT: "minor": 0,
63+
// IP-NEXT: "patch": 0
64+
// IP-NEXT: }
65+
// IP-NEXT: "domain": "macos"
66+
// IP-NEXT: "introduced": {
67+
// IP-NEXT: "major": 9,
68+
// IP-NEXT: "minor": 0,
69+
// IP-NEXT: "patch": 0
70+
// IP-NEXT: }
71+
// IP-NEXT: "obsoleted": {
72+
// IP-NEXT: "major": 20,
73+
// IP-NEXT: "minor": 0,
74+
// IP-NEXT: "patch": 0
75+
// IP-NEXT: }
76+
// IP-NEXT: }
77+
// IP-NEXT: ]
78+
79+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix MR
80+
@property int moreRestrictive __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0)));
81+
// MR-LABEL: "!testLabel": "c:objc(cs)A(py)moreRestrictive"
82+
// MR: "availability": [
83+
// MR-NEXT: {
84+
// MR-NEXT: "deprecated": {
85+
// MR-NEXT: "major": 11,
86+
// MR-NEXT: "minor": 0,
87+
// MR-NEXT: "patch": 0
88+
// MR-NEXT: }
89+
// MR-NEXT: "domain": "macos"
90+
// MR-NEXT: "introduced": {
91+
// MR-NEXT: "major": 10,
92+
// MR-NEXT: "minor": 0,
93+
// MR-NEXT: "patch": 0
94+
// MR-NEXT: }
95+
// MR-NEXT: "obsoleted": {
96+
// MR-NEXT: "major": 19,
97+
// MR-NEXT: "minor": 0,
98+
// MR-NEXT: "patch": 0
99+
// MR-NEXT: }
100+
// MR-NEXT: }
101+
// MR-NEXT: ]
102+
103+
@end
104+
105+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B
106+
__attribute__((deprecated("B is deprecated")))
107+
@interface B
108+
// B-LABEL: "!testLabel": "c:objc(cs)B"
109+
// B: "availability": [
110+
// B-NEXT: {
111+
// B-NEXT: "domain": "*"
112+
// B-NEXT: "isUnconditionallyDeprecated": true
113+
// B-NEXT: }
114+
// B-NEXT: ]
115+
116+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BIP
117+
@property int BIP;
118+
// BIP-LABEL: "!testLabel": "c:objc(cs)B(py)BIP"
119+
// BIP: "availability": [
120+
// BIP-NEXT: {
121+
// BIP-NEXT: "domain": "*"
122+
// BIP-NEXT: "isUnconditionallyDeprecated": true
123+
// BIP-NEXT: }
124+
// BIP-NEXT: ]
125+
@end
126+
127+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C
128+
__attribute__((availability(macos, unavailable)))
129+
@interface C
130+
// C-LABEL: "!testLabel": "c:objc(cs)C"
131+
// C: "availability": [
132+
// C-NEXT: {
133+
// C-NEXT: "domain": "macos"
134+
// C-NEXT: "isUnconditionallyUnavailable": true
135+
// C-NEXT: }
136+
// C-NEXT: ]
137+
138+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CIP
139+
@property int CIP;
140+
// CIP-LABEL: "!testLabel": "c:objc(cs)C(py)CIP"
141+
// CIP: "availability": [
142+
// CIP-NEXT: {
143+
// CIP-NEXT: "domain": "macos"
144+
// CIP-NEXT: "isUnconditionallyUnavailable": true
145+
// CIP-NEXT: }
146+
// CIP-NEXT: ]
147+
@end
148+
149+
@interface D
150+
// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP
151+
@property int DIP __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0)));
152+
// DIP-LABEL: "!testLabel": "c:objc(cs)D(py)DIP"
153+
// DIP: "availability": [
154+
// DIP-NEXT: {
155+
// DIP-NEXT: "deprecated": {
156+
// DIP-NEXT: "major": 11,
157+
// DIP-NEXT: "minor": 0,
158+
// DIP-NEXT: "patch": 0
159+
// DIP-NEXT: }
160+
// DIP-NEXT: "domain": "macos"
161+
// DIP-NEXT: "introduced": {
162+
// DIP-NEXT: "major": 10,
163+
// DIP-NEXT: "minor": 0,
164+
// DIP-NEXT: "patch": 0
165+
// DIP-NEXT: }
166+
// DIP-NEXT: "obsoleted": {
167+
// DIP-NEXT: "major": 19,
168+
// DIP-NEXT: "minor": 0,
169+
// DIP-NEXT: "patch": 0
170+
// DIP-NEXT: }
171+
// DIP-NEXT: }
172+
// DIP-NEXT: ]
173+
@end
174+
175+
// expected-no-diagnostics

0 commit comments

Comments
 (0)