Skip to content

Commit 4b2059c

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 #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. The fix is to allow the constraint engine to track multiple kinds of constraints per-domain which in turn ensures that the platform introduction of an availability context can be further refined even if that context is also unavailable. A better fix would be to stop diagnosing potential unavailability entirely in unavailable contexts but that change is out of scope for the 6.2 release. Resolves rdar://147945883.
1 parent 608c035 commit 4b2059c

4 files changed

+85
-22
lines changed

lib/AST/AvailabilityConstraint.cpp

+29-15
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(
4444
static bool constraintIsStronger(const AvailabilityConstraint &lhs,
4545
const AvailabilityConstraint &rhs) {
4646
DEBUG_ASSERT(lhs.getDomain() == rhs.getDomain());
47-
48-
// If the constraints have matching domains but different reasons, the
49-
// constraint with the lowest reason is "strongest".
50-
if (lhs.getReason() != rhs.getReason())
51-
return lhs.getReason() < rhs.getReason();
47+
DEBUG_ASSERT(lhs.getReason() == rhs.getReason());
5248

5349
switch (lhs.getReason()) {
5450
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
@@ -72,10 +68,12 @@ void addConstraint(llvm::SmallVector<AvailabilityConstraint, 4> &constraints,
7268

7369
auto iter = llvm::find_if(
7470
constraints, [&constraint](AvailabilityConstraint &existing) {
75-
return constraint.getDomain() == existing.getDomain();
71+
return constraint.getDomain() == existing.getDomain() &&
72+
constraint.getReason() == existing.getReason();
7673
});
7774

78-
// There's no existing constraint for the same domain so just add it.
75+
// There's no existing constraint with the same domain and reason so just add
76+
// the new one.
7977
if (iter == constraints.end()) {
8078
constraints.emplace_back(constraint);
8179
return;
@@ -91,8 +89,8 @@ std::optional<AvailabilityConstraint>
9189
DeclAvailabilityConstraints::getPrimaryConstraint() const {
9290
std::optional<AvailabilityConstraint> result;
9391

94-
auto isStrongerConstraint = [](const AvailabilityConstraint &lhs,
95-
const AvailabilityConstraint &rhs) {
92+
auto isHigherPriorityConstraint = [](const AvailabilityConstraint &lhs,
93+
const AvailabilityConstraint &rhs) {
9694
// Constraint reasons are defined in descending order of strength.
9795
if (lhs.getReason() != rhs.getReason())
9896
return lhs.getReason() < rhs.getReason();
@@ -106,7 +104,7 @@ DeclAvailabilityConstraints::getPrimaryConstraint() const {
106104

107105
// Pick the strongest constraint.
108106
for (auto const &constraint : constraints) {
109-
if (!result || isStrongerConstraint(constraint, *result))
107+
if (!result || isHigherPriorityConstraint(constraint, *result))
110108
result.emplace(constraint);
111109
}
112110

@@ -137,9 +135,9 @@ isInsideCompatibleUnavailableDeclaration(const Decl *decl,
137135
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
138136
/// use of \p decl in \p context or `std::nullopt` if there is no restriction.
139137
static std::optional<AvailabilityConstraint>
140-
getAvailabilityConstraintForAttr(const Decl *decl,
141-
const SemanticAvailableAttr &attr,
142-
const AvailabilityContext &context) {
138+
getUnavailabilityConstraintForAttr(const Decl *decl,
139+
const SemanticAvailableAttr &attr,
140+
const AvailabilityContext &context) {
143141
// Is the decl unconditionally unavailable?
144142
if (attr.isUnconditionallyUnavailable())
145143
return AvailabilityConstraint::unconditionallyUnavailable(attr);
@@ -154,6 +152,17 @@ getAvailabilityConstraintForAttr(const Decl *decl,
154152
return AvailabilityConstraint::obsoleted(attr);
155153
}
156154

155+
return std::nullopt;
156+
}
157+
158+
static std::optional<AvailabilityConstraint>
159+
getIntroductionConstraintForAttr(const Decl *decl,
160+
const SemanticAvailableAttr &attr,
161+
const AvailabilityContext &context) {
162+
auto &ctx = decl->getASTContext();
163+
auto domain = attr.getDomain();
164+
auto deploymentRange = domain.getDeploymentRange(ctx);
165+
157166
// Is the decl not yet introduced in the local context?
158167
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
159168
if (domain.supportsContextRefinement()) {
@@ -169,7 +178,6 @@ getAvailabilityConstraintForAttr(const Decl *decl,
169178
return AvailabilityConstraint::unavailableForDeployment(attr);
170179
}
171180

172-
// FIXME: [availability] Model deprecation as an availability constraint.
173181
return std::nullopt;
174182
}
175183

@@ -210,8 +218,14 @@ static void getAvailabilityConstraintsForDecl(
210218
!activePlatformDomain->contains(domain))
211219
continue;
212220

213-
if (auto constraint = getAvailabilityConstraintForAttr(decl, attr, context))
221+
if (auto constraint =
222+
getUnavailabilityConstraintForAttr(decl, attr, context))
223+
addConstraint(constraints, *constraint, ctx);
224+
225+
if (auto constraint = getIntroductionConstraintForAttr(decl, attr, context))
214226
addConstraint(constraints, *constraint, ctx);
227+
228+
// FIXME: [availability] Model deprecation as an availability constraint.
215229
}
216230

217231
// After resolving constraints, remove any constraints that indicate the

test/Sema/availability_scopes.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,27 @@ extension SomeEnum {
416416
func neverAvailable() {}
417417
}
418418

419-
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroduced()
419+
// CHECK-NEXT: {{^}} (decl version=52 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=53 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=54 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/attr/attr_availability_transitive_osx.swift

+37-4
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ func available_func_call_extension_methods(_ e: ExtendMe) { // expected-note {{a
534534
}
535535

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

539539
@available(OSX, unavailable)
540540
@available(OSX, introduced: 99)
@@ -565,18 +565,51 @@ func osx_unavailable_func(
565565
_ = OSXFutureAvailable() // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
566566
// expected-note@-1 {{add 'if #available' version check}}
567567
_ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}}
568-
_ = OSXUnavailableAndIntroducedInFuture()
568+
_ = OSXUnavailableAndIntroducedInFuture() // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}}
569+
// expected-note@-1 {{add 'if #available' version check}}
569570
_ = OSXUnavailableAndIntroducedInFutureSameAttribute()
570-
_ = OSXIntroducedInFutureAndUnavailable()
571+
_ = OSXIntroducedInFutureAndUnavailable() // expected-error {{'OSXIntroducedInFutureAndUnavailable' is only available in macOS 99 or newer}}
572+
// expected-note@-1 {{add 'if #available' version check}}
571573

572574
func takesType<T>(_ t: T.Type) {}
573575
takesType(OSXFutureAvailable.self) // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
574576
// expected-note@-1 {{add 'if #available' version check}}
575577
takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}}
578+
takesType(OSXUnavailableAndIntroducedInFuture.self) // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}}
579+
// expected-note@-1 {{add 'if #available' version check}}
580+
takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self)
581+
takesType(OSXIntroducedInFutureAndUnavailable.self) // expected-error {{'OSXIntroducedInFutureAndUnavailable' is only available in macOS 99 or newer}}
582+
// expected-note@-1 {{add 'if #available' version check}}
583+
584+
return (s1, s2, s3, s4, s5)
585+
}
586+
587+
@available(OSX, introduced: 99, unavailable)
588+
func osx_unavailable_and_future_func(
589+
_ s1: OSXFutureAvailable,
590+
_ s2: OSXObsoleted,
591+
_ s3: OSXUnavailableAndIntroducedInFuture,
592+
_ s4: OSXUnavailableAndIntroducedInFutureSameAttribute,
593+
_ s5: OSXIntroducedInFutureAndUnavailable,
594+
) -> (
595+
OSXFutureAvailable,
596+
OSXObsoleted,
597+
OSXUnavailableAndIntroducedInFuture,
598+
OSXUnavailableAndIntroducedInFutureSameAttribute,
599+
OSXIntroducedInFutureAndUnavailable
600+
) {
601+
_ = OSXFutureAvailable()
602+
_ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}}
603+
_ = OSXUnavailableAndIntroducedInFuture()
604+
_ = OSXUnavailableAndIntroducedInFutureSameAttribute()
605+
_ = OSXIntroducedInFutureAndUnavailable()
606+
607+
func takesType<T>(_ t: T.Type) {}
608+
takesType(OSXFutureAvailable.self)
609+
takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}}
576610
takesType(OSXUnavailableAndIntroducedInFuture.self)
577611
takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self)
578612
takesType(OSXIntroducedInFutureAndUnavailable.self)
579613

580614
return (s1, s2, s3, s4, s5)
581615
}
582-

test/attr/attr_availability_transitive_osx_appext.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,8 @@ func osx_func_call_extension_methods(_ e: ExtendMe) {
385385
e.osx_extension_osx_app_extension_method()
386386

387387
e.never_available_extension_osx_future_method() // expected-error {{'never_available_extension_osx_future_method()' is unavailable}}
388-
e.osx_extension_osx_future_method()
388+
e.osx_extension_osx_future_method() // expected-error {{'osx_extension_osx_future_method()' is only available in macOS 99 or newer}}
389+
// expected-note@-1 {{add 'if #available' version check}}
389390
e.osx_app_extension_extension_osx_future_method() // expected-error {{'osx_app_extension_extension_osx_future_method()' is only available in macOS 99 or newer}}
390391
// expected-note@-1 {{add 'if #available' version check}}
391392
e.osx_app_extension_extension_never_available_method() // expected-error {{'osx_app_extension_extension_never_available_method()' is unavailable}}

0 commit comments

Comments
 (0)