Skip to content

Commit 0fd2f3f

Browse files
committed
Sema: Diagnose @_spi_available on declarations that cannot be unavailable.
The attribute makes the declaration unavailable from the perspective of clients of the module's public interface and was creating a loophole that admitted inappropriate unavailability.
1 parent 4276771 commit 0fd2f3f

File tree

7 files changed

+60
-8
lines changed

7 files changed

+60
-8
lines changed

lib/Sema/TypeCheckAttr.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,6 +2338,13 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *parsedAttr) {
23382338
diagnoseAndRemoveAttr(parsedAttr, diag::spi_available_malformed);
23392339
return;
23402340
}
2341+
2342+
if (auto diag =
2343+
TypeChecker::diagnosticIfDeclCannotBeUnavailable(D, *attr)) {
2344+
diagnoseAndRemoveAttr(const_cast<AvailableAttr *>(attr->getParsedAttr()),
2345+
*diag);
2346+
return;
2347+
}
23412348
}
23422349

23432350
if (attr->isNoAsync()) {
@@ -5096,9 +5103,9 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
50965103
// declaration that is allowed to be unavailable, diagnose.
50975104
if (availabilityConstraint->isUnavailable()) {
50985105
auto attr = availabilityConstraint->getAttr();
5099-
if (auto diag = TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) {
5106+
if (auto diag = TypeChecker::diagnosticIfDeclCannotBeUnavailable(D, attr)) {
51005107
diagnoseAndRemoveAttr(const_cast<AvailableAttr *>(attr.getParsedAttr()),
5101-
diag.value());
5108+
*diag);
51025109
return;
51035110
}
51045111
}
@@ -5443,7 +5450,8 @@ TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D) {
54435450
}
54445451

54455452
std::optional<Diagnostic>
5446-
TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
5453+
TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D,
5454+
SemanticAvailableAttr attr) {
54475455
auto parentIsUnavailable = [](const Decl *D) -> bool {
54485456
if (auto *parent =
54495457
AvailabilityInference::parentDeclForInferredAvailability(D)) {
@@ -5479,7 +5487,7 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
54795487

54805488
// Be lenient in interfaces to accomodate @_spi_available, which has
54815489
// been accepted historically.
5482-
if (DC->isInSwiftinterface())
5490+
if (attr.isSPI() || DC->isInSwiftinterface())
54835491
diag.setBehaviorLimit(DiagnosticBehavior::Warning);
54845492
return diag;
54855493
}
@@ -5493,7 +5501,13 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
54935501
if (parentIsUnavailable(D))
54945502
return std::nullopt;
54955503

5496-
// Be lenient in interfaces to accomodate @_spi_available.
5504+
// @_spi_available does not make the declaration unavailable from the
5505+
// perspective of the owning module, which is what matters.
5506+
if (attr.isSPI())
5507+
return std::nullopt;
5508+
5509+
// An unavailable property with storage encountered in a swiftinterface
5510+
// might have been declared @_spi_available in source.
54975511
if (D->getDeclContext()->isInSwiftinterface())
54985512
return std::nullopt;
54995513

lib/Sema/TypeChecker.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/AST/ASTContext.h"
2121
#include "swift/AST/AccessScope.h"
2222
#include "swift/AST/AnyFunctionRef.h"
23+
#include "swift/AST/Attr.h"
2324
#include "swift/AST/AvailabilityRange.h"
2425
#include "swift/AST/AvailabilityScope.h"
2526
#include "swift/AST/DiagnosticsSema.h"
@@ -1065,7 +1066,8 @@ diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D);
10651066
/// Returns a diagnostic indicating why the declaration cannot be annotated
10661067
/// with an @available() attribute indicating it is unavailable or None if this
10671068
/// is allowed.
1068-
std::optional<Diagnostic> diagnosticIfDeclCannotBeUnavailable(const Decl *D);
1069+
std::optional<Diagnostic>
1070+
diagnosticIfDeclCannotBeUnavailable(const Decl *D, SemanticAvailableAttr attr);
10691071

10701072
/// Checks whether the required range of versions of the compilation's target
10711073
/// platform are available at the given `SourceRange`. If not, `Diagnose` is

test/Sema/availability_accessors.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,5 +569,4 @@ struct BadAccessorAvailability<T> {
569569
@available(swift, introduced: 99) // expected-error {{didSet observer for property cannot be marked unavailable with '@available'}}
570570
didSet { }
571571
}
572-
573572
}

test/Sema/availability_deinit.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class DeinitUnavailableMacOS {
3131
deinit {}
3232
}
3333

34+
class DeinitSPIAvailableMacOS {
35+
@_spi_available(macOS, introduced: 50) // expected-error {{deinitializer cannot be marked unavailable with '@available'}}
36+
deinit {}
37+
}
38+
3439
class AvailableAtDeploymentTargetDeinit {
3540
@available(macOS 50, *)
3641
deinit {}

test/Sema/availability_stored_unavailable.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55
@available(macOS, unavailable)
66
struct UnavailableMacOSStruct {} // expected-note 2 {{'UnavailableMacOSStruct' has been explicitly marked unavailable here}}
77

8+
@available(iOS, introduced: 8.0)
9+
@_spi_available(macOS, introduced: 10.9)
10+
public struct SPIAvailableMacOSStruct {}
11+
812
@available(*, unavailable)
913
public struct UniversallyUnavailableStruct {} // expected-note {{'UniversallyUnavailableStruct' has been explicitly marked unavailable here}}
1014

1115
// Ok, initialization of globals is lazy and boxed.
1216
@available(macOS, unavailable)
1317
var unavailableMacOSGlobal: UnavailableMacOSStruct = .init()
1418

19+
@available(iOS, introduced: 8.0)
20+
@_spi_available(macOS, introduced: 10.9)
21+
var spiAvailableMacOSGlobal: SPIAvailableMacOSStruct = .init()
22+
1523
@available(*, unavailable)
1624
var universallyUnavailableGlobal: UniversallyUnavailableStruct = .init()
1725

@@ -22,6 +30,10 @@ struct GoodAvailableStruct {
2230
get { UnavailableMacOSStruct() }
2331
}
2432

33+
// Ok, storage is available to implementation at runtime.
34+
@_spi_available(macOS, introduced: 10.9)
35+
var spiAvailableMacOS: SPIAvailableMacOSStruct
36+
2537
// Ok, initialization of static vars is lazy and boxed.
2638
@available(macOS, unavailable)
2739
static var staticUnavailableMacOS: UnavailableMacOSStruct = .init()
@@ -75,4 +87,7 @@ struct BadStruct {
7587
enum GoodAvailableEnum {
7688
@available(macOS, unavailable)
7789
case unavailableMacOS(UnavailableMacOSStruct)
90+
91+
@_spi_available(macOS, introduced: 10.9)
92+
case spiAvailableMacOS(SPIAvailableMacOSStruct)
7893
}

test/Sema/availability_versions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,4 +1934,13 @@ struct PropertyObservers {
19341934
@available(macOS, obsoleted: 10.9) // expected-error {{didSet observer for property cannot be marked unavailable with '@available'}}
19351935
didSet { }
19361936
}
1937+
1938+
var hasSPIAvailableObservers: Int {
1939+
@_spi_available(macOS, introduced: 10.9) // expected-error {{willSet observer for property cannot be marked unavailable with '@available'}}
1940+
willSet { }
1941+
1942+
@_spi_available(macOS, introduced: 10.9) // expected-error {{didSet observer for property cannot be marked unavailable with '@available'}}
1943+
didSet { }
1944+
}
1945+
19371946
}

test/decl/protocol/req/unavailable.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ class Foo : Proto {
1515
// Reject protocols with 'unavailable' requirements
1616
// if a protocol is not marked @objc.
1717
protocol NonObjCProto {
18-
@available(*,unavailable) func bad() // expected-error {{protocol members can only be marked unavailable in an @objc protocol}} expected-note {{protocol requires function 'bad()'}}
18+
@available(*,unavailable) // expected-error {{protocol members can only be marked unavailable in an @objc protocol}}
19+
func bad() // expected-note {{protocol requires function 'bad()'}}
20+
1921
func good()
22+
23+
@_spi_available(macOS, introduced: 10.9) // expected-warning {{protocol members can only be marked unavailable in an @objc protocol}}
24+
func kindaBad() // expected-note {{protocol requires function 'kindaBad()'}}
2025
}
2126

2227
class Bar : NonObjCProto { // expected-error {{type 'Bar' does not conform to protocol 'NonObjCProto'}} expected-note {{add stubs for conformance}}
@@ -115,4 +120,7 @@ protocol UnavailableAssoc {
115120

116121
@available(swift, obsoleted: 99)
117122
associatedtype A5
123+
124+
@_spi_available(macOS, introduced: 11) // expected-error {{associated type cannot be marked unavailable with '@available'}}
125+
associatedtype A6
118126
}

0 commit comments

Comments
 (0)