Skip to content

AST: Stop diagnosing potentially unavailable declarations in unavailable contexts #80705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@

## Swift 6.2

* The Swift compiler no longer diagnoses references to declarations that are
potentially unavailable because the platform version might not be new enough
when those references occur inside of contexts that are also unavailable to
that platform. This addresses a long-standing nuisance for multi-platform
code. However, there is also a chance that existing source code may become
ambiguous as a result:

```swift
struct A {}
struct B {}

func potentiallyAmbiguous(_: A) {}

@available(macOS 99, *)
func potentiallyAmbiguous(_: B) {}

@available(macOS, unavailable)
func unavailableOnMacOS() {
potentiallyAmbiguous(.init()) // error: ambiguous use of 'init()'
}
```

Code that is now ambiguous as a result should likely be restructured since
disambiguation based on platform introduction alone has never been a reliable
strategy, given that the code would eventually become ambiguous anyways when
the deployment target is raised.

* [SE-0419][]:
Introduced the new `Runtime` module, which contains a public API that can
generate backtraces, presently supported on macOS and Linux. Capturing a
Expand Down
63 changes: 48 additions & 15 deletions lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,59 @@ DeclAvailabilityConstraints::getPrimaryConstraint() const {
return result;
}

static bool canIgnoreConstraintInUnavailableContexts(
const Decl *decl, const AvailabilityConstraint &constraint) {
auto domain = constraint.getDomain();

switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
// Always reject uses of universally unavailable declarations, regardless
// of context, since there are no possible compilation configurations in
// which they are available. However, make an exception for types and
// conformances, which can sometimes be awkward to avoid references to.
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
if (domain.isUniversal() || domain.isSwiftLanguage())
return false;
}
return true;

case AvailabilityConstraint::Reason::PotentiallyUnavailable:
switch (domain.getKind()) {
case AvailabilityDomain::Kind::Universal:
case AvailabilityDomain::Kind::SwiftLanguage:
case AvailabilityDomain::Kind::PackageDescription:
case AvailabilityDomain::Kind::Embedded:
case AvailabilityDomain::Kind::Custom:
return false;
case AvailabilityDomain::Kind::Platform:
// Platform availability only applies to the target triple that the
// binary is being compiled for. Since the same declaration can be
// potentially unavailable from a given context when compiling for one
// platform, but available from that context when compiling for a
// different platform, it is overly strict to enforce potential platform
// unavailability constraints in contexts that are unavailable to that
// platform.
return true;
}
return constraint.getDomain().isPlatform();

case AvailabilityConstraint::Reason::Obsoleted:
case AvailabilityConstraint::Reason::UnavailableForDeployment:
return false;
}
}

static bool
isInsideCompatibleUnavailableDeclaration(const Decl *decl,
const SemanticAvailableAttr &attr,
const AvailabilityContext &context) {
shouldIgnoreConstraintInContext(const Decl *decl,
const AvailabilityConstraint &constraint,
const AvailabilityContext &context) {
if (!context.isUnavailable())
return false;

if (!attr.isUnconditionallyUnavailable())
if (!canIgnoreConstraintInUnavailableContexts(decl, constraint))
return false;

// Refuse calling universally unavailable functions from unavailable code,
// but allow the use of types.
auto domain = attr.getDomain();
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
if (domain.isUniversal() || domain.isSwiftLanguage())
return false;
}

return context.containsUnavailableDomain(domain);
return context.containsUnavailableDomain(constraint.getDomain());
}

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

Expand Down
37 changes: 37 additions & 0 deletions test/Constraints/availability_macos.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %target-typecheck-verify-swift

// REQUIRES: OS=macosx

struct A {} // expected-note * {{found this candidate}}
struct B {} // expected-note * {{found this candidate}}

func ambiguousInFarFuture(_: A) {}

@available(macOS 99, *)
func ambiguousInFarFuture(_: B) {}

struct S {
func ambiguousInFarFuture(_: A) {}
}

@available(macOS 99, *)
extension S {
func ambiguousInFarFuture(_: B) {}
}

func testDeploymentTarget(_ s: S) {
ambiguousInFarFuture(.init())
s.ambiguousInFarFuture(.init())
}

@available(macOS 99, *)
func testFarFuture(_ s: S) {
ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
s.ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
}

@available(macOS, unavailable)
func testUnavailable(_ s: S) {
ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
s.ambiguousInFarFuture(.init()) // expected-error {{ambiguous use of 'init()'}}
}
27 changes: 21 additions & 6 deletions test/Sema/availability_scopes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,19 +253,19 @@ extension SomeClass {

// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt()
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt()
// CHECK-NEXT: {{^}} (condition_following_availability version=52 unavailable=macOS
// CHECK-NEXT: {{^}} (condition_following_availability version=53 unavailable=macOS
// CHECK-NEXT: {{^}} (if_then version=53 unavailable=macOS
// CHECK-NEXT: {{^}} (condition_following_availability version=54 unavailable=macOS
// CHECK-NEXT: {{^}} (if_then version=54 unavailable=macOS
// CHECK-NEXT: {{^}} (condition_following_availability version=55 unavailable=macOS
// CHECK-NEXT: {{^}} (decl version=55 unavailable=macOS decl=funcInGuardElse()
// CHECK-NEXT: {{^}} (decl version=54 unavailable=macOS decl=funcInGuardElse()
// CHECK-NEXT: {{^}} (guard_fallthrough version=55 unavailable=macOS
// CHECK-NEXT: {{^}} (condition_following_availability version=56 unavailable=macOS
// CHECK-NEXT: {{^}} (guard_fallthrough version=56 unavailable=macOS
// CHECK-NEXT: {{^}} (decl version=57 unavailable=macOS decl=funcInInnerIfElse()
// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInOuterIfElse()
// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInInnerIfElse()
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=funcInOuterIfElse()
@available(OSX, unavailable)
extension SomeClass {
@available(OSX 51, *)
Expand Down Expand Up @@ -401,7 +401,7 @@ extension SomeEnum {
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum
// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52
// CHECK-NEXT: {{^}} (decl version=52 unavailable=macOS decl=availableMacOS_52
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable()

@available(macOS, unavailable)
Expand All @@ -418,10 +418,25 @@ extension SomeEnum {

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

@available(macOS, unavailable, introduced: 52)
@available(macOS, unavailable)
@available(macOS, introduced: 52)
func unavailableOnMacOSAndIntroduced() {
}

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

@available(macOS, introduced: 53)
@available(macOS, unavailable)
func introducedOnMacOSAndUnavailable() {
}


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

@available(macOS, unavailable, introduced: 54)
func unavailableOnMacOSAndIntroducedSameAttr() {
}

// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=NeverAvailable
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=unavailableOnMacOS()

Expand Down
12 changes: 6 additions & 6 deletions test/Sema/property_wrapper_availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct UnavailableStruct {
@UnavailableWrapper var unavailableInferred = S()

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

@available(macOS, unavailable)
Expand All @@ -117,7 +117,7 @@ struct UnavailableOnMacOSStruct {
@UnavailableWrapper var unavailableInferred = S()

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

func alwaysAvailableFunc( // expected-note 4 {{add @available attribute to enclosing global function}}
Expand Down Expand Up @@ -160,14 +160,14 @@ func unavailableFunc(
@DeprecatedWrapper _ deprecated: S,
@UnavailableWrapper _ unavailable: S,
@WrappedValueUnavailableOnMacOS _ unavailableWrappedValue: S,
@WrappedValueAvailable51 _ wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
@WrappedValueAvailable51 _ wrappedValueAavailable51: S
) {
@AlwaysAvailableWrapper var alwaysAvailableLocal = S()
@Available51Wrapper var available51Local = S()
@DeprecatedWrapper var deprecatedLocal = S()
@UnavailableWrapper var unavailableLocal = S()
@WrappedValueUnavailableOnMacOS var unavailableWrappedValueLocal = S()
@WrappedValueAvailable51 var wrappedValueAavailable51 = S() // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
@WrappedValueAvailable51 var wrappedValueAavailable51 = S()
}

@available(macOS, unavailable)
Expand All @@ -177,12 +177,12 @@ func unavailableOnMacOSFunc(
@DeprecatedWrapper _ deprecated: S,
@UnavailableWrapper _ unavailable: S,
@WrappedValueUnavailableOnMacOS _ unavailableWrappedValue: S,
@WrappedValueAvailable51 _ wrappedValueAavailable51: S // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
@WrappedValueAvailable51 _ wrappedValueAavailable51: S
) {
@AlwaysAvailableWrapper var alwaysAvailableLocal = S()
@Available51Wrapper var available51Local = S()
@DeprecatedWrapper var deprecatedLocal = S()
@UnavailableWrapper var unavailableLocal = S()
@WrappedValueUnavailableOnMacOS var unavailableWrappedValueLocal = S()
@WrappedValueAvailable51 var wrappedValueAavailable51 = S() // expected-error {{'wrappedValue' is only available in macOS 51 or newer}}
@WrappedValueAvailable51 var wrappedValueAavailable51 = S()
}
Loading