Skip to content

Commit 8985e88

Browse files
committed
AST: Track platform unavailability and introduction as separate constraints
Potential unavailability of a declaration is always diagnosed in a context that does not have a sufficient platform introduction constraint, even if that context is also unavailable on that platform. This behavior is overly strict, since the potential unavailability will never matter, but it's a longstanding quirk of availability checking. As a result, some source code has been written to work around this quirk by marking declarations as simultaneously unavailable and introduced for a given platform: ``` @available(macOS, unavailable, introduced: 15) func unavailableAndIntroducedInMacOS15() { // ... allowed to call functions introduced in macOS 15. } ``` When availability checking was refactored to be based on a constraint engine in swiftlang#79260, the compiler started effectively treating `@available(macOS, unavailable, introduced: 15)` as just `@available(macOS, unavailable)` because the introduction constraint was treated as lower priority and therefore superseded by the unavailability constraint. This caused a regression for the code that was written to work around the availability checker's strictness. We could try to match the behavior from previous releases, but it's actually tricky to match the behavior well enough in the new availability checking architecture to fully fix source compatibility. Consequently, it seems like the best fix is actually to address this long standing issue and stop diagnosing potential unavailability in unavailable contexts. The main risk of this approach is source compatibility for regions of unavailable code. It's theoretically possible that restricting available declarations by introduction version in unavailable contexts is important to prevent ambiguities during overload resolution in some codebases. If we find that is a problem that is too prevalent, we may have to take a different approach. Resolves rdar://147945883.
1 parent 21480b8 commit 8985e88

6 files changed

+124
-75
lines changed

lib/AST/AvailabilityConstraint.cpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,59 @@ DeclAvailabilityConstraints::getPrimaryConstraint() const {
113113
return result;
114114
}
115115

116+
static bool canIgnoreConstraintInUnavailableContexts(
117+
const Decl *decl, const AvailabilityConstraint &constraint) {
118+
auto domain = constraint.getDomain();
119+
120+
switch (constraint.getReason()) {
121+
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
122+
// Always reject uses of universally unavailable declarations, regardless
123+
// of context, since there are no possible compilation configurations in
124+
// which they are available. However, make an exception for types and
125+
// conformances, which can sometimes be awkward to avoid references to.
126+
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
127+
if (domain.isUniversal() || domain.isSwiftLanguage())
128+
return false;
129+
}
130+
return true;
131+
132+
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
133+
switch (domain.getKind()) {
134+
case AvailabilityDomain::Kind::Universal:
135+
case AvailabilityDomain::Kind::SwiftLanguage:
136+
case AvailabilityDomain::Kind::PackageDescription:
137+
case AvailabilityDomain::Kind::Embedded:
138+
case AvailabilityDomain::Kind::Custom:
139+
return false;
140+
case AvailabilityDomain::Kind::Platform:
141+
// Platform availability only applies to the target triple that the
142+
// binary is being compiled for. Since the same declaration can be
143+
// potentially unavailable from a given context when compiling for one
144+
// platform, but available from that context when compiling for a
145+
// different platform, it is overly strict to enforce potential platform
146+
// unavailability constraints in contexts that are unavailable to that
147+
// platform.
148+
return true;
149+
}
150+
return constraint.getDomain().isPlatform();
151+
152+
case AvailabilityConstraint::Reason::Obsoleted:
153+
case AvailabilityConstraint::Reason::UnavailableForDeployment:
154+
return false;
155+
}
156+
}
157+
116158
static bool
117-
isInsideCompatibleUnavailableDeclaration(const Decl *decl,
118-
const SemanticAvailableAttr &attr,
119-
const AvailabilityContext &context) {
159+
shouldIgnoreConstraintInContext(const Decl *decl,
160+
const AvailabilityConstraint &constraint,
161+
const AvailabilityContext &context) {
120162
if (!context.isUnavailable())
121163
return false;
122164

123-
if (!attr.isUnconditionallyUnavailable())
165+
if (!canIgnoreConstraintInUnavailableContexts(decl, constraint))
124166
return false;
125167

126-
// Refuse calling universally unavailable functions from unavailable code,
127-
// but allow the use of types.
128-
auto domain = attr.getDomain();
129-
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
130-
if (domain.isUniversal() || domain.isSwiftLanguage())
131-
return false;
132-
}
133-
134-
return context.containsUnavailableDomain(domain);
168+
return context.containsUnavailableDomain(constraint.getDomain());
135169
}
136170

137171
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
@@ -218,8 +252,7 @@ static void getAvailabilityConstraintsForDecl(
218252
// declaration is unconditionally unavailable in a domain for which
219253
// the context is already unavailable.
220254
llvm::erase_if(constraints, [&](const AvailabilityConstraint &constraint) {
221-
return isInsideCompatibleUnavailableDeclaration(decl, constraint.getAttr(),
222-
context);
255+
return shouldIgnoreConstraintInContext(decl, constraint, context);
223256
});
224257
}
225258

test/Sema/availability_scopes.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,19 +253,19 @@ extension SomeClass {
253253

254254
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
255255
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass
256-
// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt()
256+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt()
257257
// CHECK-NEXT: {{^}} (condition_following_availability version=52 unavailable=macOS
258258
// CHECK-NEXT: {{^}} (condition_following_availability version=53 unavailable=macOS
259259
// CHECK-NEXT: {{^}} (if_then version=53 unavailable=macOS
260260
// CHECK-NEXT: {{^}} (condition_following_availability version=54 unavailable=macOS
261261
// CHECK-NEXT: {{^}} (if_then version=54 unavailable=macOS
262262
// CHECK-NEXT: {{^}} (condition_following_availability version=55 unavailable=macOS
263-
// CHECK-NEXT: {{^}} (decl version=55 unavailable=macOS decl=funcInGuardElse()
263+
// CHECK-NEXT: {{^}} (decl version=54 unavailable=macOS decl=funcInGuardElse()
264264
// CHECK-NEXT: {{^}} (guard_fallthrough version=55 unavailable=macOS
265265
// CHECK-NEXT: {{^}} (condition_following_availability version=56 unavailable=macOS
266266
// CHECK-NEXT: {{^}} (guard_fallthrough version=56 unavailable=macOS
267-
// CHECK-NEXT: {{^}} (decl version=57 unavailable=macOS decl=funcInInnerIfElse()
268-
// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInOuterIfElse()
267+
// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInInnerIfElse()
268+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=funcInOuterIfElse()
269269
@available(OSX, unavailable)
270270
extension SomeClass {
271271
@available(OSX 51, *)
@@ -401,7 +401,7 @@ extension SomeEnum {
401401
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum
402402
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum
403403
// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52
404-
// CHECK-NEXT: {{^}} (decl version=52 unavailable=macOS decl=availableMacOS_52
404+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52
405405
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable()
406406

407407
@available(macOS, unavailable)

test/Sema/property_wrapper_availability.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ struct UnavailableStruct {
9999
@UnavailableWrapper var unavailableInferred = S()
100100

101101
@WrappedValueUnavailableOnMacOS var unavailableWrappedValue: S
102-
@WrappedValueAvailable51 var wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
102+
@WrappedValueAvailable51 var wrappedValueAavailable51: S
103103
}
104104

105105
@available(macOS, unavailable)
@@ -117,7 +117,7 @@ struct UnavailableOnMacOSStruct {
117117
@UnavailableWrapper var unavailableInferred = S()
118118

119119
@WrappedValueUnavailableOnMacOS var unavailableWrappedValue: S
120-
@WrappedValueAvailable51 var wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
120+
@WrappedValueAvailable51 var wrappedValueAavailable51: S
121121
}
122122

123123
func alwaysAvailableFunc( // expected-note 4 {{add @available attribute to enclosing global function}}
@@ -160,14 +160,14 @@ func unavailableFunc(
160160
@DeprecatedWrapper _ deprecated: S,
161161
@UnavailableWrapper _ unavailable: S,
162162
@WrappedValueUnavailableOnMacOS _ unavailableWrappedValue: S,
163-
@WrappedValueAvailable51 _ wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
163+
@WrappedValueAvailable51 _ wrappedValueAavailable51: S
164164
) {
165165
@AlwaysAvailableWrapper var alwaysAvailableLocal = S()
166166
@Available51Wrapper var available51Local = S()
167167
@DeprecatedWrapper var deprecatedLocal = S()
168168
@UnavailableWrapper var unavailableLocal = S()
169169
@WrappedValueUnavailableOnMacOS var unavailableWrappedValueLocal = S()
170-
@WrappedValueAvailable51 var wrappedValueAavailable51 = S() // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
170+
@WrappedValueAvailable51 var wrappedValueAavailable51 = S()
171171
}
172172

173173
@available(macOS, unavailable)
@@ -177,12 +177,12 @@ func unavailableOnMacOSFunc(
177177
@DeprecatedWrapper _ deprecated: S,
178178
@UnavailableWrapper _ unavailable: S,
179179
@WrappedValueUnavailableOnMacOS _ unavailableWrappedValue: S,
180-
@WrappedValueAvailable51 _ wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
180+
@WrappedValueAvailable51 _ wrappedValueAavailable51: S
181181
) {
182182
@AlwaysAvailableWrapper var alwaysAvailableLocal = S()
183183
@Available51Wrapper var available51Local = S()
184184
@DeprecatedWrapper var deprecatedLocal = S()
185185
@UnavailableWrapper var unavailableLocal = S()
186186
@WrappedValueUnavailableOnMacOS var unavailableWrappedValueLocal = S()
187-
@WrappedValueAvailable51 var wrappedValueAavailable51 = S() // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
187+
@WrappedValueAvailable51 var wrappedValueAavailable51 = S()
188188
}

test/attr/attr_availability_transitive_osx.swift

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ func never_available_func(
8787
) {
8888
always()
8989
never() // expected-error {{'never()' is unavailable}}
90-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
91-
// expected-note@-1 {{add 'if #available' version check}}
90+
osx_future()
9291
osx()
9392
osx_ios()
9493
osx_extension()
@@ -105,8 +104,7 @@ func osx_func(
105104
) {
106105
always()
107106
never() // expected-error {{'never()' is unavailable}}
108-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
109-
// expected-note@-1 {{add 'if #available' version check}}
107+
osx_future()
110108
osx()
111109
osx_ios()
112110
osx_extension()
@@ -159,7 +157,7 @@ var never_var: (
159157
) = (
160158
always(),
161159
never(), // expected-error {{'never()' is unavailable}}
162-
osx_future(), // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
160+
osx_future(),
163161
osx(),
164162
osx_ios(),
165163
osx_extension()
@@ -176,7 +174,7 @@ var osx_var: (
176174
) = (
177175
always(),
178176
never(), // expected-error {{'never()' is unavailable}}
179-
osx_future(), // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
177+
osx_future(),
180178
osx(),
181179
osx_ios(),
182180
osx_extension()
@@ -218,7 +216,7 @@ struct AlwaysAvailableContainer { // expected-note 2 {{add @available attribute
218216
struct NeverAvailableContainer { // expected-note 2 {{'NeverAvailableContainer' has been explicitly marked unavailable here}}
219217
let always_var: AlwaysAvailable = always()
220218
let never_var: NeverAvailable = never() // expected-error {{'never()' is unavailable}}
221-
let osx_future_var: OSXFutureAvailable = osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
219+
let osx_future_var: OSXFutureAvailable = osx_future()
222220
let osx_var: OSXUnavailable = osx()
223221
let osx_ios_var: MultiPlatformUnavailable = osx_ios()
224222
let osx_extension_var: OSXAppExtensionsUnavailable = osx_extension()
@@ -228,7 +226,7 @@ struct NeverAvailableContainer { // expected-note 2 {{'NeverAvailableContainer'
228226
struct OSXUnavailableContainer { // expected-note 2 {{'OSXUnavailableContainer' has been explicitly marked unavailable here}}
229227
let always_var: AlwaysAvailable = always()
230228
let never_var: NeverAvailable = never() // expected-error {{'never()' is unavailable}}
231-
let osx_future_var: OSXFutureAvailable = osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
229+
let osx_future_var: OSXFutureAvailable = osx_future()
232230
let osx_var: OSXUnavailable = osx()
233231
let osx_ios_var: MultiPlatformUnavailable = osx_ios()
234232
let osx_extension_var: OSXAppExtensionsUnavailable = osx_extension()
@@ -301,8 +299,7 @@ extension ExtendMe {
301299
) {
302300
always()
303301
never() // expected-error {{'never()' is unavailable}}
304-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
305-
// expected-note@-1 {{add 'if #available' version check}}
302+
osx_future()
306303
osx()
307304
osx_ios()
308305
osx_extension()
@@ -319,8 +316,7 @@ extension ExtendMe {
319316
) {
320317
always()
321318
never() // expected-error {{'never()' is unavailable}}
322-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
323-
// expected-note@-1 {{add 'if #available' version check}}
319+
osx_future()
324320
osx()
325321
osx_ios()
326322
osx_extension()
@@ -337,15 +333,14 @@ extension ExtendMe {
337333
) {
338334
always()
339335
never() // expected-error {{'never()' is unavailable}}
340-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
341-
// expected-note@-1 {{add 'if #available' version check}}
336+
osx_future()
342337
osx()
343338
osx_ios()
344339
osx_extension()
345340
}
346341

347342
@available(OSXApplicationExtension, unavailable)
348-
func never_available_extension_osx_app_extension_method( // expected-note {{add @available attribute to enclosing instance method}}
343+
func never_available_extension_osx_app_extension_method(
349344
_: AlwaysAvailable,
350345
_: NeverAvailable,
351346
_: OSXFutureAvailable,
@@ -355,8 +350,7 @@ extension ExtendMe {
355350
) {
356351
always()
357352
never() // expected-error {{'never()' is unavailable}}
358-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
359-
// expected-note@-1 {{add 'if #available' version check}}
353+
osx_future()
360354
osx()
361355
osx_ios()
362356
osx_extension()
@@ -380,8 +374,7 @@ extension ExtendMe {
380374
) {
381375
always()
382376
never() // expected-error {{'never()' is unavailable}}
383-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
384-
// expected-note@-1 {{add 'if #available' version check}}
377+
osx_future()
385378
osx()
386379
osx_ios()
387380
osx_extension()
@@ -398,8 +391,7 @@ extension ExtendMe {
398391
) {
399392
always()
400393
never() // expected-error {{'never()' is unavailable}}
401-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
402-
// expected-note@-1 {{add 'if #available' version check}}
394+
osx_future()
403395
osx()
404396
osx_ios()
405397
osx_extension()
@@ -416,15 +408,14 @@ extension ExtendMe {
416408
) {
417409
always()
418410
never() // expected-error {{'never()' is unavailable}}
419-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
420-
// expected-note@-1 {{add 'if #available' version check}}
411+
osx_future()
421412
osx()
422413
osx_ios()
423414
osx_extension()
424415
}
425416

426417
@available(OSXApplicationExtension, unavailable)
427-
func osx_extension_osx_app_extension_method( // expected-note {{add @available attribute to enclosing instance method}}
418+
func osx_extension_osx_app_extension_method(
428419
_: AlwaysAvailable,
429420
_: NeverAvailable,
430421
_: OSXFutureAvailable,
@@ -434,8 +425,7 @@ extension ExtendMe {
434425
) {
435426
always()
436427
never() // expected-error {{'never()' is unavailable}}
437-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
438-
// expected-note@-1 {{add 'if #available' version check}}
428+
osx_future()
439429
osx()
440430
osx_ios()
441431
osx_extension()
@@ -477,8 +467,7 @@ extension ExtendMe { // expected-note * {{add @available attribute to enclosing
477467
) {
478468
always()
479469
never() // expected-error {{'never()' is unavailable}}
480-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
481-
// expected-note@-1 {{add 'if #available' version check}}
470+
osx_future()
482471
osx()
483472
osx_ios()
484473
osx_extension()
@@ -495,8 +484,7 @@ extension ExtendMe { // expected-note * {{add @available attribute to enclosing
495484
) {
496485
always()
497486
never() // expected-error {{'never()' is unavailable}}
498-
osx_future() // expected-error {{'osx_future()' is only available in macOS 99 or newer}}
499-
// expected-note@-1 {{add 'if #available' version check}}
487+
osx_future()
500488
osx()
501489
osx_ios()
502490
osx_extension()
@@ -534,7 +522,7 @@ func available_func_call_extension_methods(_ e: ExtendMe) { // expected-note {{a
534522
}
535523

536524
@available(OSX, obsoleted: 10.9)
537-
struct OSXObsoleted {} // expected-note 2 {{'OSXObsoleted' was obsoleted in macOS 10.9}}
525+
struct OSXObsoleted {} // expected-note 4 {{'OSXObsoleted' was obsoleted in macOS 10.9}}
538526

539527
@available(OSX, unavailable)
540528
@available(OSX, introduced: 99)
@@ -561,17 +549,46 @@ func osx_unavailable_func(
561549
OSXUnavailableAndIntroducedInFutureSameAttribute,
562550
OSXIntroducedInFutureAndUnavailable
563551
) {
564-
// FIXME: [availability] Stop diagnosing potential unavailability or obsoletion in an unavailable context.
565-
_ = OSXFutureAvailable() // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
566-
// expected-note@-1 {{add 'if #available' version check}}
552+
// FIXME: [availability] Stop diagnosing obsoletion in an unavailable context.
553+
_ = OSXFutureAvailable()
567554
_ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}}
568555
_ = OSXUnavailableAndIntroducedInFuture()
569556
_ = OSXUnavailableAndIntroducedInFutureSameAttribute()
570557
_ = OSXIntroducedInFutureAndUnavailable()
571558

572559
func takesType<T>(_ t: T.Type) {}
573-
takesType(OSXFutureAvailable.self) // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
574-
// expected-note@-1 {{add 'if #available' version check}}
560+
takesType(OSXFutureAvailable.self)
561+
takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}}
562+
takesType(OSXUnavailableAndIntroducedInFuture.self)
563+
takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self)
564+
takesType(OSXIntroducedInFutureAndUnavailable.self)
565+
566+
return (s1, s2, s3, s4, s5)
567+
}
568+
569+
@available(OSX, unavailable, introduced: 99)
570+
func osx_unavailable_and_introduced_func(
571+
_ s1: OSXFutureAvailable,
572+
_ s2: OSXObsoleted,
573+
_ s3: OSXUnavailableAndIntroducedInFuture,
574+
_ s4: OSXUnavailableAndIntroducedInFutureSameAttribute,
575+
_ s5: OSXIntroducedInFutureAndUnavailable,
576+
) -> (
577+
OSXFutureAvailable,
578+
OSXObsoleted,
579+
OSXUnavailableAndIntroducedInFuture,
580+
OSXUnavailableAndIntroducedInFutureSameAttribute,
581+
OSXIntroducedInFutureAndUnavailable
582+
) {
583+
// FIXME: [availability] Stop diagnosing obsoletion in an unavailable context.
584+
_ = OSXFutureAvailable()
585+
_ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}}
586+
_ = OSXUnavailableAndIntroducedInFuture()
587+
_ = OSXUnavailableAndIntroducedInFutureSameAttribute()
588+
_ = OSXIntroducedInFutureAndUnavailable()
589+
590+
func takesType<T>(_ t: T.Type) {}
591+
takesType(OSXFutureAvailable.self)
575592
takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}}
576593
takesType(OSXUnavailableAndIntroducedInFuture.self)
577594
takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self)

0 commit comments

Comments
 (0)