Files
swift-mirror/test/Sema/availability_scopes.swift
Allan Shortlidge ae21f8d390 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
https://github.com/swiftlang/swift/pull/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.
2025-04-11 11:50:29 -07:00

461 lines
14 KiB
Swift

// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -target %target-cpu-apple-macos50 > %t.dump 2>&1
// RUN: %FileCheck --strict-whitespace %s < %t.dump
// REQUIRES: OS=macosx
// CHECK: {{^}}(root version=50
// CHECK-NEXT: {{^}} (decl version=51 decl=SomeClass
// CHECK-NEXT: {{^}} (decl version=52 decl=someMethod()
// CHECK-NEXT: {{^}} (decl version=53 decl=someInnerFunc()
// CHECK-NEXT: {{^}} (decl version=53 decl=InnerClass
// CHECK-NEXT: {{^}} (decl version=54 decl=innerClassMethod
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticProperty
// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticProperty
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyInferredType
// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyInferredType
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=multiPatternStaticPropertyA
// CHECK-NEXT: {{^}} (decl version=52 decl=multiPatternStaticPropertyA
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someComputedProperty
// CHECK-NEXT: {{^}} (decl version=52 decl=someComputedProperty
// CHECK-NEXT: {{^}} (decl version=52 decl=someOtherMethod()
@available(OSX 51, *)
class SomeClass {
@available(OSX 52, *)
func someMethod() {
@available(OSX 53, *)
func someInnerFunc() { }
@available(OSX 53, *)
class InnerClass {
@available(OSX 54, *)
func innerClassMethod() { }
}
}
func someUnrefinedMethod() { }
@available(OSX 52, *)
static var someStaticProperty: Int = 7
@available(OSX 52, *)
static var someStaticPropertyInferredType = 7
@available(OSX 52, *)
static var multiPatternStaticPropertyA = 7,
multiPatternStaticPropertyB = 8
@available(OSX 52, *)
var someComputedProperty: Int {
get { }
set(v) { }
}
@available(OSX 52, *)
func someOtherMethod() { }
}
// CHECK-NEXT: {{^}} (decl version=51 decl=someFunction()
@available(OSX 51, *)
func someFunction() { }
// CHECK-NEXT: {{^}} (decl version=51 decl=SomeProtocol
// CHECK-NEXT: {{^}} (decl version=52 decl=protoMethod()
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=protoProperty
// CHECK-NEXT: {{^}} (decl version=52 decl=protoProperty
@available(OSX 51, *)
protocol SomeProtocol {
@available(OSX 52, *)
func protoMethod() -> Int
@available(OSX 52, *)
var protoProperty: Int { get }
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=52 decl=someExtensionFunction()
@available(OSX 51, *)
extension SomeClass {
@available(OSX 52, *)
func someExtensionFunction() { }
}
// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithStmtCondition
// CHECK-NEXT: {{^}} (condition_following_availability version=52
// CHECK-NEXT: {{^}} (condition_following_availability version=53
// CHECK-NEXT: {{^}} (if_then version=53
// CHECK-NEXT: {{^}} (condition_following_availability version=54
// CHECK-NEXT: {{^}} (if_then version=54
// CHECK-NEXT: {{^}} (condition_following_availability version=55
// CHECK-NEXT: {{^}} (decl version=55 decl=funcInGuardElse()
// CHECK-NEXT: {{^}} (guard_fallthrough version=55
// CHECK-NEXT: {{^}} (condition_following_availability version=56
// CHECK-NEXT: {{^}} (guard_fallthrough version=56
// CHECK-NEXT: {{^}} (decl version=57 decl=funcInInnerIfElse()
// CHECK-NEXT: {{^}} (decl version=53 decl=funcInOuterIfElse()
@available(OSX 51, *)
func functionWithStmtCondition() {
if #available(OSX 52, *),
let x = (nil as Int?),
#available(OSX 53, *) {
if #available(OSX 54, *) {
guard #available(OSX 55, *) else {
@available(OSX 55, *)
func funcInGuardElse() { }
}
guard #available(OSX 56, *) else { }
} else {
@available(OSX 57, *)
func funcInInnerIfElse() { }
}
} else {
@available(OSX 53, *)
func funcInOuterIfElse() { }
}
}
// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtCondition
// CHECK-NEXT: {{^}} (condition_following_availability version=53
// CHECK-NEXT: {{^}} (if_then version=53
// CHECK-NEXT: {{^}} (condition_following_availability version=54
// CHECK-NEXT: {{^}} (if_then version=54
@available(OSX 51, *)
func functionWithUnnecessaryStmtCondition() {
// Shouldn't introduce availability scope for then branch when unnecessary
if #available(OSX 51, *) {
}
if #available(OSX 10.9, *) {
}
// Nested in conjunctive statement condition
if #available(OSX 53, *),
let x = (nil as Int?),
#available(OSX 52, *) {
}
if #available(OSX 54, *),
#available(OSX 54, *) {
}
// Wildcard is same as minimum deployment target
if #available(iOS 7.0, *) {
}
}
// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithUnnecessaryStmtConditionsHavingElseBranch
// CHECK-NEXT: {{^}} (if_else version=none
// CHECK-NEXT: {{^}} (decl version=none decl=funcInInnerIfElse()
// CHECK-NEXT: {{^}} (if_else version=none
// CHECK-NEXT: {{^}} (guard_else version=none
// CHECK-NEXT: {{^}} (guard_else version=none
// CHECK-NEXT: {{^}} (if_else version=none
@available(OSX 51, *)
func functionWithUnnecessaryStmtConditionsHavingElseBranch(p: Int?) {
// Else branch context version is bottom when check is unnecessary
if #available(OSX 51, *) {
} else {
if #available(OSX 52, *) {
}
@available(OSX 52, *)
func funcInInnerIfElse() { }
if #available(iOS 7.0, *) {
} else {
}
}
if #available(iOS 7.0, *) {
} else {
}
guard #available(iOS 8.0, *) else { }
// Else branch will execute if p is nil, so it is not dead.
if #available(iOS 7.0, *),
let x = p {
} else {
}
if #available(iOS 7.0, *),
let x = p,
#available(iOS 7.0, *) {
} else {
}
// Else branch is dead
guard #available(iOS 7.0, *),
#available(iOS 8.0, *) else { }
if #available(OSX 51, *),
#available(OSX 51, *) {
} else {
}
}
// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithWhile()
// CHECK-NEXT: {{^}} (condition_following_availability version=52
// CHECK-NEXT: {{^}} (while_body version=52
// CHECK-NEXT: {{^}} (decl version=54 decl=funcInWhileBody()
@available(OSX 51, *)
func functionWithWhile() {
while #available(OSX 52, *),
let x = (nil as Int?) {
@available(OSX 54, *)
func funcInWhileBody() { }
}
}
// CHECK-NEXT: {{^}} (decl version=51 decl=functionWithDefer()
// CHECK-NEXT: {{^}} (condition_following_availability version=52
// CHECK-NEXT: {{^}} (if_then version=52
@available(OSX 51, *)
func functionWithDefer() {
defer {
if #available(OSX 52, *) {}
}
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInit
// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInit
// CHECK-NEXT: {{^}} (condition_following_availability version=54
// CHECK-NEXT: {{^}} (if_then version=54
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someStaticPropertyWithClosureInitInferred
// CHECK-NEXT: {{^}} (decl version=52 decl=someStaticPropertyWithClosureInitInferred
// CHECK-NEXT: {{^}} (condition_following_availability version=54
// CHECK-NEXT: {{^}} (if_then version=54
@available(OSX 51, *)
extension SomeClass {
@available(OSX 52, *)
static var someStaticPropertyWithClosureInit: Int = {
if #available(OSX 54, *) {
return 54
}
return 53
}()
@available(OSX 52, *)
static var someStaticPropertyWithClosureInitInferred = {
if #available(OSX 54, *) {
return 54
}
return 53
}()
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass
// 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=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=53 unavailable=macOS decl=funcInInnerIfElse()
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=funcInOuterIfElse()
@available(OSX, unavailable)
extension SomeClass {
@available(OSX 51, *)
func functionWithStmtConditionsInUnavailableExt() {
if #available(OSX 52, *),
let x = (nil as Int?),
#available(OSX 53, *) {
if #available(OSX 54, *) {
guard #available(OSX 55, *) else {
@available(OSX 55, *)
func funcInGuardElse() { }
}
guard #available(OSX 56, *) else { }
} else {
@available(OSX 57, *)
func funcInInnerIfElse() { }
}
} else {
@available(OSX 53, *)
func funcInOuterIfElse() { }
}
}
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=wrappedValue
@propertyWrapper
struct Wrapper<T> {
var wrappedValue: T
}
// CHECK-NEXT: {{^}} (decl version=51 decl=SomeStruct
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someLazyVar
// CHECK-NEXT: {{^}} (condition_following_availability version=52
// CHECK-NEXT: {{^}} (guard_fallthrough version=52
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=someWrappedVar
// CHECK-NEXT: {{^}} (condition_following_availability version=52
// CHECK-NEXT: {{^}} (guard_fallthrough version=52
// CHECK-NEXT: {{^}} (decl version=52 decl=someMethodAvailable52()
@available(OSX 51, *)
struct SomeStruct {
lazy var someLazyVar = {
guard #available(OSX 52, *) else {
return someMethod()
}
return someMethodAvailable52()
}()
@Wrapper var someWrappedVar = {
guard #available(OSX 52, *) else {
return 51
}
return 52
}()
func someMethod() -> Int { return 51 }
@available(OSX 52, *)
func someMethodAvailable52() -> Int { return 52 }
}
// CHECK-NEXT: {{^}} (decl version=51 decl=SomeEnum
// CHECK-NEXT: {{^}} (decl version=52 decl=a
// CHECK-NEXT: {{^}} (decl version=53 decl=b
@available(OSX 51, *)
enum SomeEnum {
@available(OSX 52, *)
case a
@available(OSX 53, *)
case b, c
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=someComputedGlobalVar
// CHECK-NEXT: {{^}} (decl version=51 decl=_
// CHECK-NEXT: {{^}} (decl version=52 decl=_
var someComputedGlobalVar: Int {
@available(OSX 51, *)
get { 1 }
@available(OSX 52, *)
set { }
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=interpolated
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=string
func testStringInterpolation() {
let interpolated = """
\([""].map {
let string = $0
return string
})
"""
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=result
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedA
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=unusedB
func testSequenceExprs(b: Bool, x: Int?) {
let result = b
? x.map {
let unusedA: Int
return $0
}
: x.map {
let unusedB: Int
return $0
}
}
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOS()
// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=x
@available(macOS, unavailable)
func unavailableOnMacOS() {
let x = 1
}
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum
// CHECK-NEXT: {{^}} (decl version=51 decl=extension.SomeEnum
// CHECK-NEXT: {{^}} (decl_implicit version=51 decl=unavailableOnMacOS
// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=unavailableOnMacOS
@available(OSX 51, *)
extension SomeEnum {
@available(macOS, unavailable)
var unavailableOnMacOS: Int { 1 }
}
// 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=50 unavailable=macOS decl=availableMacOS_52
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable()
@available(macOS, unavailable)
extension SomeEnum {
@available(OSX 52, *)
var availableMacOS_52: Int { 1 }
@available(macOSApplicationExtension, unavailable)
func unavailableInAppExtensions() {}
@available(*, unavailable)
func neverAvailable() {}
}
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=unavailableOnMacOSAndIntroduced()
@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()
@available(*, unavailable)
struct NeverAvailable {
@available(macOS, unavailable)
func unavailableOnMacOS() {}
}
// CHECK-NEXT: {{^}} (decl version=50 deprecated decl=deprecatedOnMacOS()
// CHECK-NEXT: {{^}} (decl_implicit version=50 deprecated decl=x
@available(macOS, deprecated)
func deprecatedOnMacOS() {
let x = 1
}
// CHECK-NEXT: {{^}} (decl version=51 decl=FinalDecl
@available(OSX 51, *)
typealias FinalDecl = Int