Skip to content

Commit 56f7ffd

Browse files
authored
Merge pull request #79858 from tshortli/spi-available-attr-diagnostics
Sema: Diagnose `@_spi_available` on declarations that cannot be unavailable
2 parents a596f23 + 0fd2f3f commit 56f7ffd

8 files changed

+84
-30
lines changed

lib/Sema/TypeCheckAttr.cpp

Lines changed: 34 additions & 18 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,24 +5103,11 @@ 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)) {
5100-
diagnose(attr.getParsedAttr()->getLocation(), diag.value());
5106+
if (auto diag = TypeChecker::diagnosticIfDeclCannotBeUnavailable(D, attr)) {
5107+
diagnoseAndRemoveAttr(const_cast<AvailableAttr *>(attr.getParsedAttr()),
5108+
*diag);
51015109
return;
51025110
}
5103-
5104-
if (auto *PD = dyn_cast<ProtocolDecl>(D->getDeclContext())) {
5105-
if (auto *VD = dyn_cast<ValueDecl>(D)) {
5106-
if (VD->isProtocolRequirement() && !PD->isObjC()) {
5107-
diagnoseAndRemoveAttr(
5108-
const_cast<AvailableAttr *>(attr.getParsedAttr()),
5109-
diag::unavailable_method_non_objc_protocol)
5110-
.warnInSwiftInterface(D->getDeclContext());
5111-
// Be lenient in interfaces to accomodate @_spi_available, which has
5112-
// been accepted historically.
5113-
return;
5114-
}
5115-
}
5116-
}
51175111
}
51185112

51195113
// If the decl is potentially unavailable relative to its parent and it's
@@ -5456,7 +5450,8 @@ TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D) {
54565450
}
54575451

54585452
std::optional<Diagnostic>
5459-
TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
5453+
TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D,
5454+
SemanticAvailableAttr attr) {
54605455
auto parentIsUnavailable = [](const Decl *D) -> bool {
54615456
if (auto *parent =
54625457
AvailabilityInference::parentDeclForInferredAvailability(D)) {
@@ -5484,14 +5479,35 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
54845479
return Diagnostic(diag::availability_decl_no_unavailable, D);
54855480
}
54865481

5482+
auto DC = D->getDeclContext();
5483+
if (auto *PD = dyn_cast<ProtocolDecl>(DC)) {
5484+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
5485+
if (VD->isProtocolRequirement() && !PD->isObjC()) {
5486+
auto diag = Diagnostic(diag::unavailable_method_non_objc_protocol);
5487+
5488+
// Be lenient in interfaces to accomodate @_spi_available, which has
5489+
// been accepted historically.
5490+
if (attr.isSPI() || DC->isInSwiftinterface())
5491+
diag.setBehaviorLimit(DiagnosticBehavior::Warning);
5492+
return diag;
5493+
}
5494+
}
5495+
}
5496+
54875497
if (auto *VD = dyn_cast<VarDecl>(D)) {
54885498
if (!VD->hasStorageOrWrapsStorage())
54895499
return std::nullopt;
54905500

54915501
if (parentIsUnavailable(D))
54925502
return std::nullopt;
54935503

5494-
// 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.
54955511
if (D->getDeclContext()->isInSwiftinterface())
54965512
return std::nullopt;
54975513

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_script_mode.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ struct Available50 {}
1111
struct Available51 {}
1212

1313
@available(macOS, unavailable)
14-
struct UnavailableOnMacOS {}
14+
struct UnavailableOnMacOS {} // expected-note {{'UnavailableOnMacOS' has been explicitly marked unavailable here}}
1515

1616
@available(*, unavailable)
17-
struct UnavailableUnconditionally {}
17+
struct UnavailableUnconditionally {} // expected-note {{'UnavailableUnconditionally' has been explicitly marked unavailable here}}
1818

1919
var alwaysAvailableVar: AlwaysAvailable = .init() // Ok
2020

@@ -28,7 +28,7 @@ var availableOn50Var: Available50 = .init() // Ok
2828
var potentiallyUnavailableVar: Available51 = .init()
2929

3030
@available(macOS, unavailable) // expected-error {{global variable cannot be marked unavailable with '@available' in script mode}}
31-
var unavailableOnMacOSVar: UnavailableOnMacOS = .init()
31+
var unavailableOnMacOSVar: UnavailableOnMacOS = .init() // expected-error {{'UnavailableOnMacOS' is unavailable in macOS}}
3232

3333
@available(*, unavailable) // expected-error {{global variable cannot be marked unavailable with '@available' in script mode}}
34-
var unconditionallyUnavailableVar: UnavailableUnconditionally = .init()
34+
var unconditionallyUnavailableVar: UnavailableUnconditionally = .init() // expected-error {{'UnavailableUnconditionally' is unavailable}}

test/Sema/availability_stored_unavailable.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
// REQUIRES: OS=macosx
44

55
@available(macOS, unavailable)
6-
struct UnavailableMacOSStruct {}
6+
struct UnavailableMacOSStruct {} // expected-note 2 {{'UnavailableMacOSStruct' has been explicitly marked unavailable here}}
7+
8+
@available(iOS, introduced: 8.0)
9+
@_spi_available(macOS, introduced: 10.9)
10+
public struct SPIAvailableMacOSStruct {}
711

812
@available(*, unavailable)
9-
public struct UniversallyUnavailableStruct {}
13+
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()
@@ -61,18 +73,21 @@ struct GoodUniversallyUnavailableStruct {
6173
struct BadStruct {
6274
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
6375
@available(macOS, unavailable)
64-
var unavailableMacOS: UnavailableMacOSStruct = .init()
76+
var unavailableMacOS: UnavailableMacOSStruct = .init() // expected-error {{'UnavailableMacOSStruct' is unavailable in macOS}}
6577

6678
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
6779
@available(macOS, unavailable)
68-
lazy var lazyUnavailableMacOS: UnavailableMacOSStruct = .init()
80+
lazy var lazyUnavailableMacOS: UnavailableMacOSStruct = .init() // expected-error {{'UnavailableMacOSStruct' is unavailable in macOS}}
6981

7082
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
7183
@available(*, unavailable)
72-
var universallyUnavailable: UniversallyUnavailableStruct = .init()
84+
var universallyUnavailable: UniversallyUnavailableStruct = .init() // expected-error {{'UniversallyUnavailableStruct' is unavailable}}
7385
}
7486

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)