Skip to content

Commit 96e6e50

Browse files
committed
Fix @available(macCatalyst N, iOS N+M) bug
Previously, availability checking computed a declaration’s availability as the intersection of all @available attributes with active platforms. This meant that Swift would not allow you to use an API that was available earlier in a child platform than in its parent until it was also available in the parent platform. That was incorrect. This PR corrects availability checking to find the most specific @available attribute with an introduced version and use that instead. Fixes rdar://60892534.
1 parent c1ab69e commit 96e6e50

File tree

2 files changed

+28
-13
lines changed

2 files changed

+28
-13
lines changed

lib/AST/Availability.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ void AvailabilityInference::applyInferredAvailableAttrs(
134134

135135
Optional<AvailabilityContext>
136136
AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
137-
Optional<AvailabilityContext> AnnotatedRange;
137+
const AvailableAttr *bestAvailAttr = nullptr;
138138

139139
for (auto Attr : D->getAttrs()) {
140140
auto *AvailAttr = dyn_cast<AvailableAttr>(Attr);
@@ -145,21 +145,19 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
145145
continue;
146146
}
147147

148-
AvailabilityContext AttrRange{
149-
VersionRange::allGTE(AvailAttr->Introduced.getValue())};
150-
151-
// If we have multiple introduction versions, we will conservatively
152-
// assume the worst case scenario. We may want to be more precise here
153-
// in the future or emit a diagnostic.
154-
155-
if (AnnotatedRange.hasValue()) {
156-
AnnotatedRange.getValue().intersectWith(AttrRange);
157-
} else {
158-
AnnotatedRange = AttrRange;
148+
// Okay, we have a candidate, but is it better than one we already found?
149+
if (!bestAvailAttr ||
150+
inheritsAvailabilityFromPlatform(AvailAttr->Platform,
151+
bestAvailAttr->Platform)) {
152+
bestAvailAttr = AvailAttr;
159153
}
160154
}
161155

162-
return AnnotatedRange;
156+
if (!bestAvailAttr)
157+
return None;
158+
159+
return AvailabilityContext{
160+
VersionRange::allGTE(bestAvailAttr->Introduced.getValue())};
163161
}
164162

165163
AvailabilityContext AvailabilityInference::availableRange(const Decl *D,

test/attr/attr_availability_maccatalyst.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,34 +48,51 @@ deprecatedOnIOSButNotMacCatalyst() // no-warning
4848
func introducedLaterOnMacCatalyst() {
4949
}
5050

51+
@available(iOS 57.0, macCatalyst 56.0, *)
52+
func introducedLaterOnIOS() {
53+
}
54+
5155
// expected-note@+1 *{{add @available attribute to enclosing global function}}
5256
func testPoundAvailable() {
5357

5458
if #available(macCatalyst 55.0, *) {
5559
introducedLaterOnMacCatalyst() // expected-error {{'introducedLaterOnMacCatalyst()' is only available in Mac Catalyst 56.0 or newer}}
5660
// expected-note@-1 {{add 'if #available' version check}}
61+
introducedLaterOnIOS() // expected-error {{'introducedLaterOnIOS()' is only available in Mac Catalyst 56.0 or newer}}
62+
// expected-note@-1 {{add 'if #available' version check}}
5763
}
5864

5965
// macCatalyst should win over iOS when present
6066

6167
if #available(iOS 56.0, macCatalyst 55.0, *) {
6268
introducedLaterOnMacCatalyst() // expected-error {{'introducedLaterOnMacCatalyst()' is only available in Mac Catalyst 56.0 or newer}}
6369
// expected-note@-1 {{add 'if #available' version check}}
70+
introducedLaterOnIOS() // expected-error {{'introducedLaterOnIOS()' is only available in Mac Catalyst 56.0 or newer}}
71+
// expected-note@-1 {{add 'if #available' version check}}
6472
}
6573

6674
if #available(iOS 55.0, macCatalyst 56.0, *) {
6775
introducedLaterOnMacCatalyst() // no-warning
76+
introducedLaterOnIOS() // no-error
77+
}
78+
79+
if #available(iOS 57.0, macCatalyst 56.0, *) {
80+
introducedLaterOnMacCatalyst() // no-warning
81+
introducedLaterOnIOS() // no-error
6882
}
6983

7084
// iOS availability should be inherited when macCatalyst is not present
7185

7286
if #available(iOS 55.0, *) {
7387
introducedLaterOnMacCatalyst() // expected-error {{'introducedLaterOnMacCatalyst()' is only available in Mac Catalyst 56.0 or newer}}
7488
// expected-note@-1 {{add 'if #available' version check}}
89+
introducedLaterOnIOS() // expected-error {{'introducedLaterOnIOS()' is only available in Mac Catalyst 56.0 or newer}}
90+
// expected-note@-1 {{add 'if #available' version check}}
7591
}
7692

7793
if #available(iOS 56.0, *) {
7894
introducedLaterOnMacCatalyst() // no-warning
95+
introducedLaterOnIOS() // no-error
7996
}
8097

8198
// macOS availability doesn't count on macCatalyst for Swift.

0 commit comments

Comments
 (0)