Skip to content

Commit 64fadaf

Browse files
committed
AST: Stop diagnosing potentially unavailable declarations in unavailable contexts.
Potential unavailability of a declaration has always been diagnosed in contexts that do not have a sufficient platform introduction constraint, even when those contexts are also unavailable on the target 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 #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 c8609c7 commit 64fadaf

8 files changed

+204
-76
lines changed

CHANGELOG.md

+27
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@
55
66
## Swift 6.2
77

8+
* The Swift compiler no longer diagnoses references to declarations that are
9+
potentially unavailable because the platform version might not be new enough
10+
when those references occur inside of contexts that are also unavailable to
11+
that platform. This addresses a long-standing nuisance for multi-platform
12+
code. However, there is also a chance that existing source code may become
13+
ambiguous as a result:
14+
15+
```swift
16+
struct A {}
17+
struct B {}
18+
19+
func potentiallyAmbiguous(_: A) {}
20+
21+
@available(macOS 99, *)
22+
func potentiallyAmbiguous(_: B) {}
23+
24+
@available(macOS, unavailable)
25+
func unavailableOnMacOS() {
26+
potentiallyAmbiguous(.init()) // error: ambiguous use of 'init()'
27+
}
28+
```
29+
30+
Code that is now ambiguous as a result should likely be restructured since
31+
disambiguation based on platform introduction alone has never been a reliable
32+
strategy, given that the code would eventually become ambiguous anyways when
33+
the deployment target is raised.
34+
835
* [SE-0419][]:
936
Introduced the new `Runtime` module, which contains a public API that can
1037
generate backtraces, presently supported on macOS and Linux. Capturing a

lib/AST/AvailabilityConstraint.cpp

+48-15
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

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
// REQUIRES: OS=macosx
4+
5+
struct A {} // expected-note * {{found this candidate}}
6+
struct B {} // expected-note * {{found this candidate}}
7+
8+
func ambiguousInFarFuture(_: A) {}
9+
10+
@available(macOS 99, *)
11+
func ambiguousInFarFuture(_: B) {}
12+
13+
struct S {
14+
func ambiguousInFarFuture(_: A) {}
15+
}
16+
17+
@available(macOS 99, *)
18+
extension S {
19+
func ambiguousInFarFuture(_: B) {}
20+
}
21+
22+
func testDeploymentTarget(_ s: S) {
23+
ambiguousInFarFuture(.init())
24+
s.ambiguousInFarFuture(.init())
25+
}
26+
27+
@available(macOS 99, *)
28+
func testFarFuture(_ s: S) {
29+
ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
30+
s.ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
31+
}
32+
33+
@available(macOS, unavailable)
34+
func testUnavailable(_ s: S) {
35+
ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
36+
s.ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
37+
}

test/Sema/availability_scopes.swift

+21-6
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)
@@ -418,10 +418,25 @@ extension SomeEnum {
418418

419419
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroduced()
420420

421-
@available(macOS, unavailable, introduced: 52)
421+
@available(macOS, unavailable)
422+
@available(macOS, introduced: 52)
422423
func unavailableOnMacOSAndIntroduced() {
423424
}
424425

426+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=introducedOnMacOSAndUnavailable()
427+
428+
@available(macOS, introduced: 53)
429+
@available(macOS, unavailable)
430+
func introducedOnMacOSAndUnavailable() {
431+
}
432+
433+
434+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroducedSameAttr()
435+
436+
@available(macOS, unavailable, introduced: 54)
437+
func unavailableOnMacOSAndIntroducedSameAttr() {
438+
}
439+
425440
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=NeverAvailable
426441
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=unavailableOnMacOS()
427442

test/Sema/property_wrapper_availability.swift

+6-6
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
}

0 commit comments

Comments
 (0)