Skip to content

Commit c78090b

Browse files
committed
Sema: When computing potential unavailability of a decl, first check whether the decl is explicitly unavailable and the context is also unavailable. If those conditions are met, treat the decl as if it were always available since unavailable code is allowed to reference unavailable decls.
Previously, `TypeChecker::checkDeclarationAvailability()` would behave this way for most explicitly unavailable decls by _accident_. An explicitly unavailable decl has no introduction version, and the existing logic therefore would fail to find a lower bound on the availability of the decl. The edge case that exposed the fragility of this logic was an unavailable extension containing a member with it's own explicit availability. The unavailability of the extension ought to trump the availability of the member, but the existing logic couldn't detect that. The compiler also ought to diagnose the conflicting availability annotations but I'd like to address that separately. Resolves rdar://92551870
1 parent b9be8b0 commit c78090b

File tree

3 files changed

+90
-31
lines changed

3 files changed

+90
-31
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,30 @@ static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC,
333333
.isContainedIn(AvailabilityContext::forDeploymentTarget(ctx));
334334
}
335335

336+
/// Returns true if the reference or any of its parents is an
337+
/// unconditional unavailable declaration for the same platform.
338+
static bool isInsideCompatibleUnavailableDeclaration(
339+
const Decl *D, const ExportContext &where, const AvailableAttr *attr) {
340+
auto referencedPlatform = where.getUnavailablePlatformKind();
341+
if (!referencedPlatform)
342+
return false;
343+
344+
if (!attr->isUnconditionallyUnavailable()) {
345+
return false;
346+
}
347+
348+
// Refuse calling unavailable functions from unavailable code,
349+
// but allow the use of types.
350+
PlatformKind platform = attr->Platform;
351+
if (platform == PlatformKind::none && !isa<TypeDecl>(D) &&
352+
!isa<ExtensionDecl>(D)) {
353+
return false;
354+
}
355+
356+
return (*referencedPlatform == platform ||
357+
inheritsAvailabilityFromPlatform(platform, *referencedPlatform));
358+
}
359+
336360
namespace {
337361

338362
/// A class to walk the AST to build the type refinement context hierarchy.
@@ -1191,6 +1215,12 @@ bool TypeChecker::isDeclarationUnavailable(
11911215
Optional<UnavailabilityReason>
11921216
TypeChecker::checkDeclarationAvailability(const Decl *D,
11931217
const ExportContext &Where) {
1218+
// Skip computing potential unavailability if the declaration is explicitly
1219+
// unavailable and the context is also unavailable.
1220+
if (const AvailableAttr *Attr = AvailableAttr::isUnavailable(D))
1221+
if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr))
1222+
return None;
1223+
11941224
if (isDeclarationUnavailable(D, Where.getDeclContext(), [&Where] {
11951225
return Where.getAvailabilityContext();
11961226
})) {
@@ -2013,33 +2043,6 @@ const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
20132043
return nullptr;
20142044
}
20152045

2016-
/// Returns true if the reference or any of its parents is an
2017-
/// unconditional unavailable declaration for the same platform.
2018-
static bool isInsideCompatibleUnavailableDeclaration(
2019-
const Decl *D, const ExportContext &where,
2020-
const AvailableAttr *attr) {
2021-
auto referencedPlatform = where.getUnavailablePlatformKind();
2022-
if (!referencedPlatform)
2023-
return false;
2024-
2025-
if (!attr->isUnconditionallyUnavailable()) {
2026-
return false;
2027-
}
2028-
2029-
// Refuse calling unavailable functions from unavailable code,
2030-
// but allow the use of types.
2031-
PlatformKind platform = attr->Platform;
2032-
if (platform == PlatformKind::none &&
2033-
!isa<TypeDecl>(D) &&
2034-
!isa<ExtensionDecl>(D)) {
2035-
return false;
2036-
}
2037-
2038-
return (*referencedPlatform == platform ||
2039-
inheritsAvailabilityFromPlatform(platform,
2040-
*referencedPlatform));
2041-
}
2042-
20432046
static void fixItAvailableAttrRename(InFlightDiagnostic &diag,
20442047
SourceRange referenceRange,
20452048
const ValueDecl *renamedDecl,

test/Sema/availability_versions.swift

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,10 +1632,6 @@ func funcWithMultipleShortFormAnnotationsForTheSamePlatform() {
16321632
// expected-note@-1 {{add 'if #available' version check}}
16331633
}
16341634

1635-
@available(OSX 10.9, *)
1636-
@available(OSX, unavailable)
1637-
func unavailableWins() { } // expected-note {{'unavailableWins()' has been explicitly marked unavailable here}}
1638-
16391635
func useShortFormAvailable() {
16401636
// expected-note@-1 4{{add @available attribute to enclosing global function}}
16411637

@@ -1654,6 +1650,34 @@ func useShortFormAvailable() {
16541650

16551651
funcWithMultipleShortFormAnnotationsForTheSamePlatform() // expected-error {{'funcWithMultipleShortFormAnnotationsForTheSamePlatform()' is only available in macOS 10.53 or newer}}
16561652
// expected-note@-1 {{add 'if #available' version check}}
1653+
}
1654+
1655+
// Unavailability takes precedence over availability and is inherited
1656+
1657+
@available(OSX 10.9, *)
1658+
@available(OSX, unavailable)
1659+
func unavailableWins() { }
1660+
// expected-note@-1 {{'unavailableWins()' has been explicitly marked unavailable here}}
1661+
1662+
struct HasUnavailableExtension {
1663+
@available(OSX, unavailable)
1664+
public func directlyUnavailable() { }
1665+
// expected-note@-1 {{'directlyUnavailable()' has been explicitly marked unavailable here}}
1666+
}
1667+
1668+
@available(OSX, unavailable)
1669+
extension HasUnavailableExtension {
1670+
public func inheritsUnavailable() { }
1671+
// expected-note@-1 {{'inheritsUnavailable()' has been explicitly marked unavailable here}}
1672+
1673+
@available(OSX 10.9, *)
1674+
public func moreAvailableButStillUnavailable() { }
1675+
// expected-note@-1 {{'moreAvailableButStillUnavailable()' has been explicitly marked unavailable here}}
1676+
}
16571677

1678+
func useHasUnavailableExtension(_ s: HasUnavailableExtension) {
16581679
unavailableWins() // expected-error {{'unavailableWins()' is unavailable}}
1680+
s.directlyUnavailable() // expected-error {{'directlyUnavailable()' is unavailable}}
1681+
s.inheritsUnavailable() // expected-error {{'inheritsUnavailable()' is unavailable in macOS}}
1682+
s.moreAvailableButStillUnavailable() // expected-error {{'moreAvailableButStillUnavailable()' is unavailable in macOS}}
16591683
}

test/attr/attr_availability_transitive_osx.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,39 @@ extension Outer {
106106

107107
@available(OSX, unavailable)
108108
extension Outer {
109+
// expected-note@+1 {{'outer_osx_init_osx' has been explicitly marked unavailable here}}
109110
static var outer_osx_init_osx = osx() // OK
111+
112+
// expected-note@+1 {{'osx_call_osx()' has been explicitly marked unavailable here}}
113+
func osx_call_osx() {
114+
osx() // OK
115+
}
116+
117+
func osx_call_osx_extension() {
118+
osx_extension() // OK; osx_extension is only unavailable if -application-extension is passed.
119+
}
120+
121+
func takes_and_returns_osx(_ x: NotOnOSX) -> NotOnOSX {
122+
return x // OK
123+
}
124+
125+
// This @available should be ignored; inherited unavailability takes precedence
126+
@available(OSX 999, *)
127+
// expected-note@+1 {{'osx_more_available_but_still_unavailable_call_osx()' has been explicitly marked unavailable here}}
128+
func osx_more_available_but_still_unavailable_call_osx() {
129+
osx() // OK
130+
}
131+
132+
// rdar://92551870
133+
func osx_call_osx_more_available_but_still_unavailable() {
134+
osx_more_available_but_still_unavailable_call_osx() // OK
135+
}
136+
}
137+
138+
func takesOuter(_ o: Outer) {
139+
_ = Outer.outer_osx_init_osx // expected-error {{'outer_osx_init_osx' is unavailable in macOS}}
140+
o.osx_call_osx() // expected-error {{'osx_call_osx()' is unavailable in macOS}}
141+
o.osx_more_available_but_still_unavailable_call_osx() // expected-error {{'osx_more_available_but_still_unavailable_call_osx()' is unavailable in macOS}}
110142
}
111143

112144
@available(OSX, unavailable)

0 commit comments

Comments
 (0)