Skip to content

Commit ba3a9c0

Browse files
committed
Sema: Use the deployment target when checking availability for SPI and unavailable API declarations with -target-min-inlining-version min specified. There's not much benefit to more accurate enforcement of availability in these decls since API clients can't use them and there's a lot of existing code that would be needlessly diagnosed without these exceptions.
Resolves rdar://92716633
1 parent 23f39db commit ba3a9c0

File tree

2 files changed

+84
-95
lines changed

2 files changed

+84
-95
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ bool swift::isExported(const ValueDecl *VD) {
7979
return false;
8080
}
8181

82+
static bool hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
83+
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
84+
for (const ProtocolDecl *PD : protocols) {
85+
AccessScope scope =
86+
PD->getFormalAccessScope(/*useDC*/ nullptr,
87+
/*treatUsableFromInlineAsPublic*/ true);
88+
if (scope.isPublic())
89+
return true;
90+
}
91+
92+
return false;
93+
}
94+
8295
bool swift::isExported(const ExtensionDecl *ED) {
8396
// An extension can only be exported if it extends an exported type.
8497
if (auto *NTD = ED->getExtendedNominal()) {
@@ -94,14 +107,8 @@ bool swift::isExported(const ExtensionDecl *ED) {
94107

95108
// If the extension declares a conformance to a public protocol then the
96109
// extension is exported.
97-
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
98-
for (const ProtocolDecl *PD : protocols) {
99-
AccessScope scope =
100-
PD->getFormalAccessScope(/*useDC*/nullptr,
101-
/*treatUsableFromInlineAsPublic*/true);
102-
if (scope.isPublic())
103-
return true;
104-
}
110+
if (hasConformancesToPublicProtocols(ED))
111+
return true;
105112

106113
return false;
107114
}
@@ -313,7 +320,7 @@ static bool hasActiveAvailableAttribute(Decl *D,
313320
return getActiveAvailableAttribute(D, AC);
314321
}
315322

316-
static bool bodyIsResilienceBoundary(Decl *D) {
323+
static bool shouldConstrainBodyToDeploymentTarget(Decl *D) {
317324
// The declaration contains code...
318325
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
319326
// And it has a location so we can check it...
@@ -526,9 +533,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
526533
AvailabilityContext DeclInfo = ExplicitDeclInfo;
527534
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
528535

529-
// If the entire declaration is surrounded by a resilience boundary, it is
530-
// also constrained by the deployment target.
531-
if (signatureIsResilienceBoundary(D))
536+
if (shouldConstrainSignatureToDeploymentTarget(D))
532537
DeclInfo.intersectWith(AvailabilityContext::forDeploymentTarget(Context));
533538

534539
SourceRange Range = refinementSourceRangeForDecl(D);
@@ -562,9 +567,10 @@ class TypeRefinementContextBuilder : private ASTWalker {
562567
}
563568

564569
// No need to introduce a context if the declaration does not have an
565-
// availability attribute and the signature is not a resilience boundary.
570+
// availability attribute and the signature does not constrain availability
571+
// to the deployment target.
566572
if (!hasActiveAvailableAttribute(D, Context) &&
567-
!signatureIsResilienceBoundary(D)) {
573+
!shouldConstrainSignatureToDeploymentTarget(D)) {
568574
return false;
569575
}
570576

@@ -581,12 +587,23 @@ class TypeRefinementContextBuilder : private ASTWalker {
581587
return true;
582588
}
583589

584-
/// A declaration's signature is a resilience boundary if the entire
585-
/// declaration--not just the body--is not ABI-public and it's in a context
586-
/// where ABI-public declarations would be available below the minimum
587-
/// deployment target.
588-
bool signatureIsResilienceBoundary(Decl *D) {
589-
return !isCurrentTRCContainedByDeploymentTarget() && !::isExported(D);
590+
/// Checks whether the entire declaration, including its signature, should be
591+
/// constrained to the deployment target. Generally public API declarations
592+
/// are not constrained since they appear in the interface of the module and
593+
/// may be consumed by clients with lower deployment targets, but there are
594+
/// some exceptions.
595+
bool shouldConstrainSignatureToDeploymentTarget(Decl *D) {
596+
if (isCurrentTRCContainedByDeploymentTarget())
597+
return false;
598+
599+
// As a convenience, SPI decls and explicitly unavailable decls are
600+
// constrained to the deployment target. There's not much benefit to
601+
// checking these declarations at a lower availability version floor since
602+
// neither can be used by API clients.
603+
if (D->isSPI() || AvailableAttr::isUnavailable(D))
604+
return true;
605+
606+
return !::isExported(D);
590607
}
591608

592609
/// Returns the source range which should be refined by declaration. This
@@ -633,14 +650,14 @@ class TypeRefinementContextBuilder : private ASTWalker {
633650
}
634651

635652
bool bodyIntroducesNewContext(Decl *D) {
636-
// Are we already effectively in a resilience boundary? If not, adding one
637-
// wouldn't change availability.
653+
// Are we already constrained by the deployment target? If not, adding a
654+
// new context wouldn't change availability.
638655
if (isCurrentTRCContainedByDeploymentTarget())
639656
return false;
640657

641-
// If we're in a function, is its body a resilience boundary?
658+
// If we're in a function, check if it ought to use the deployment target.
642659
if (auto afd = dyn_cast<AbstractFunctionDecl>(D))
643-
return bodyIsResilienceBoundary(afd);
660+
return shouldConstrainBodyToDeploymentTarget(afd);
644661

645662
// The only other case we care about is top-level code.
646663
return isa<TopLevelCodeDecl>(D);
@@ -4089,14 +4106,7 @@ void swift::checkExplicitAvailability(Decl *decl) {
40894106
return false;
40904107
});
40914108

4092-
auto protocols = extension->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
4093-
auto hasProtocols = std::any_of(protocols.begin(), protocols.end(),
4094-
[](const ProtocolDecl *PD) -> bool {
4095-
AccessScope scope =
4096-
PD->getFormalAccessScope(/*useDC*/nullptr,
4097-
/*treatUsableFromInlineAsPublic*/true);
4098-
return scope.isPublic();
4099-
});
4109+
auto hasProtocols = hasConformancesToPublicProtocols(extension);
41004110

41014111
if (!hasMembers && !hasProtocols) return;
41024112

test/attr/attr_inlinable_available.swift

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,12 @@ public func alwaysUnavailable(
321321
}
322322

323323
@_spi(Private)
324-
public func spiDeployedUseNoAvailable( // expected-note 5 {{add @available attribute}}
324+
public func spiDeployedUseNoAvailable( // expected-note 3 {{add @available attribute}}
325325
_: NoAvailable,
326326
_: BeforeInliningTarget,
327327
_: AtInliningTarget,
328-
// FIXME: Next two should be accepted (SPI)
329-
_: BetweenTargets, // expected-error {{'BetweenTargets' is only available in}}
330-
_: AtDeploymentTarget, // expected-error {{'AtDeploymentTarget' is only available in}}
328+
_: BetweenTargets,
329+
_: AtDeploymentTarget,
331330
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
332331
) {
333332
defer {
@@ -533,16 +532,14 @@ public func spiDeployedUseNoAvailable( // expected-note 5 {{add @available attri
533532
_: Unavailable
534533
) {
535534
defer {
536-
// FIXME: Should be allowed for compatibility (unavailable)
537-
_ = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
535+
_ = AtDeploymentTarget()
538536
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
539537
}
540538
_ = NoAvailable()
541539
_ = BeforeInliningTarget()
542540
_ = AtInliningTarget()
543-
// FIXME: Next two should be accepted for compatibility (unavailable)
544-
_ = BetweenTargets() // expected-error {{'BetweenTargets' is only available in}} expected-note {{add 'if #available'}}
545-
_ = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
541+
_ = BetweenTargets()
542+
_ = AtDeploymentTarget()
546543
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
547544
_ = Unavailable()
548545

@@ -558,26 +555,23 @@ public func spiDeployedUseNoAvailable( // expected-note 5 {{add @available attri
558555
}
559556

560557
@_spi(Private)
561-
@inlinable public func spiInlinedUseNoAvailable( // expected-note 8 {{add @available attribute}}
558+
@inlinable public func spiInlinedUseNoAvailable( // expected-note 3 {{add @available attribute}}
562559
_: NoAvailable,
563560
_: BeforeInliningTarget,
564561
_: AtInliningTarget,
565-
// FIXME: Next two should be accepted (SPI)
566-
_: BetweenTargets, // expected-error {{'BetweenTargets' is only available in}}
567-
_: AtDeploymentTarget, // expected-error {{'AtDeploymentTarget' is only available in}}
562+
_: BetweenTargets,
563+
_: AtDeploymentTarget,
568564
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
569565
) {
570566
defer {
571-
// FIXME: Should be allowed (SPI)
572-
_ = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
567+
_ = AtDeploymentTarget()
573568
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
574569
}
575570
_ = NoAvailable()
576571
_ = BeforeInliningTarget()
577572
_ = AtInliningTarget()
578-
// FIXME: Next two should be accepted (SPI)
579-
_ = BetweenTargets() // expected-error {{'BetweenTargets' is only available in}} expected-note {{add 'if #available'}}
580-
_ = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
573+
_ = BetweenTargets()
574+
_ = AtDeploymentTarget()
581575
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
582576

583577
if #available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) {
@@ -679,21 +673,19 @@ public func defaultArgsUseUnavailable(
679673
_: Any = NoAvailable.self,
680674
_: Any = BeforeInliningTarget.self,
681675
_: Any = AtInliningTarget.self,
682-
// FIXME: Next two should be accepted for compatibility (unavailable)
683-
_: Any = BetweenTargets.self, // expected-error {{'BetweenTargets' is only available in}}
684-
_: Any = AtDeploymentTarget.self, // expected-error {{'AtDeploymentTarget' is only available in}}
676+
_: Any = BetweenTargets.self,
677+
_: Any = AtDeploymentTarget.self,
685678
_: Any = AfterDeploymentTarget.self, // expected-error {{'AfterDeploymentTarget' is only available in}}
686679
_: Any = Unavailable.self
687680
) {}
688681

689682
@_spi(Private)
690-
public func spiDefaultArgsUseNoAvailable( // expected-note 3 {{add @available attribute}}
683+
public func spiDefaultArgsUseNoAvailable( // expected-note 1 {{add @available attribute}}
691684
_: Any = NoAvailable.self,
692685
_: Any = BeforeInliningTarget.self,
693686
_: Any = AtInliningTarget.self,
694-
// FIXME: Next two should be accepted (SPI)
695-
_: Any = BetweenTargets.self, // expected-error {{'BetweenTargets' is only available in}}
696-
_: Any = AtDeploymentTarget.self, // expected-error {{'AtDeploymentTarget' is only available in}}
687+
_: Any = BetweenTargets.self,
688+
_: Any = AtDeploymentTarget.self,
697689
_: Any = AfterDeploymentTarget.self // expected-error {{'AfterDeploymentTarget' is only available in}}
698690
) {}
699691

@@ -806,18 +798,16 @@ public struct UnavailablePublicStruct {
806798
public var aPublic: NoAvailable
807799
public var bPublic: BeforeInliningTarget
808800
public var cPublic: AtInliningTarget
809-
// FIXME: Next two should be accepted for compatibility (unavailable)
810-
public var dPublic: BetweenTargets // expected-error {{'BetweenTargets' is only available in}}
811-
public var ePublic: AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}}
801+
public var dPublic: BetweenTargets
802+
public var ePublic: AtDeploymentTarget
812803
public var fPublic: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
813804
public var gPublic: Unavailable
814805

815806
public var aPublicInit: Any = NoAvailable()
816807
public var bPublicInit: Any = BeforeInliningTarget()
817808
public var cPublicInit: Any = AtInliningTarget()
818-
// FIXME: The next two should not be accepted, the default initializer is not inlined
819-
public var dPublicInit: Any = BetweenTargets() // expected-error {{'BetweenTargets' is only available in}}
820-
public var ePublicInit: Any = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}}
809+
public var dPublicInit: Any = BetweenTargets()
810+
public var ePublicInit: Any = AtDeploymentTarget()
821811
public var fPublicInit: Any = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}}
822812
public var gPublicInit: Any = Unavailable()
823813

@@ -831,21 +821,19 @@ public struct UnavailablePublicStruct {
831821
}
832822

833823
@_spi(Private)
834-
public struct SPIStruct { // expected-note 7 {{add @available attribute}}
824+
public struct SPIStruct { // expected-note 3 {{add @available attribute}}
835825
public var aPublic: NoAvailable
836826
public var bPublic: BeforeInliningTarget
837827
public var cPublic: AtInliningTarget
838-
// FIXME: Next two should be accepted (SPI)
839-
public var dPublic: BetweenTargets // expected-error {{'BetweenTargets' is only available in}}
840-
public var ePublic: AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}}
828+
public var dPublic: BetweenTargets
829+
public var ePublic: AtDeploymentTarget
841830
public var fPublic: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
842831

843832
public var aPublicInit: Any = NoAvailable()
844833
public var bPublicInit: Any = BeforeInliningTarget()
845834
public var cPublicInit: Any = AtInliningTarget()
846-
// FIXME: The next two should not be diagnosed, the initializers are not inlined
847-
public var dPublicInit: Any = BetweenTargets() // expected-error {{'BetweenTargets' is only available in}}
848-
public var ePublicInit: Any = AtDeploymentTarget() // expected-error {{'AtDeploymentTarget' is only available in}}
835+
public var dPublicInit: Any = BetweenTargets()
836+
public var ePublicInit: Any = AtDeploymentTarget()
849837
public var fPublicInit: Any = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}}
850838

851839
var aInternal: NoAvailable = .init()
@@ -917,15 +905,13 @@ extension BetweenTargets {
917905
public func publicFunc3() {}
918906
}
919907

920-
// FIXME: Should be accepted (SPI)
921-
// expected-warning@+1 {{BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
908+
// FIXME: Can we prevent this warning when SPI members are the reason the extension is exported?
909+
// expected-warning@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
922910
extension BetweenTargets {
923911
@_spi(Private)
924912
public func spiFunc1() {}
925913
}
926914

927-
// FIXME: Should be accepted (SPI)
928-
// expected-warning@+2 {{BetweenTargets' is only available in}} expected-note@+2 {{add @available attribute to enclosing extension}}
929915
@_spi(Private)
930916
extension BetweenTargets {
931917
internal func internalFunc3() {}
@@ -950,17 +936,14 @@ extension BetweenTargets {
950936
) { }
951937
}
952938

953-
// FIXME: Should be accepted (SPI)
954-
// expected-warning@+2 {{BetweenTargets' is only available in}}
955939
@_spi(Private)
956-
extension BetweenTargets { // expected-note 4 {{add @available attribute to enclosing extension}}
957-
public func inheritsSPINoAvailable( // expected-note 3 {{add @available attribute to enclosing instance method}}
940+
extension BetweenTargets { // expected-note 1 {{add @available attribute to enclosing extension}}
941+
public func inheritsSPINoAvailable( // expected-note 1 {{add @available attribute to enclosing instance method}}
958942
_: NoAvailable,
959943
_: BeforeInliningTarget,
960944
_: AtInliningTarget,
961-
// FIXME: Next two should be accepted (SPI)
962-
_: BetweenTargets, // expected-error {{'BetweenTargets' is only available in}}
963-
_: AtDeploymentTarget, // expected-error {{'AtDeploymentTarget' is only available in}}
945+
_: BetweenTargets,
946+
_: AtDeploymentTarget,
964947
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
965948
) { }
966949
}
@@ -1081,21 +1064,19 @@ public protocol UnavailableProtoWithAssoc {
10811064
associatedtype A: NoAvailableProto
10821065
associatedtype B: BeforeInliningTargetProto
10831066
associatedtype C: AtInliningTargetProto
1084-
// FIXME: Next two should be accepted for compatibility (unavailable)
1085-
associatedtype D: BetweenTargetsProto // expected-error {{'BetweenTargetsProto' is only available in}}
1086-
associatedtype E: AtDeploymentTargetProto // expected-error {{'AtDeploymentTargetProto' is only available in}}
1067+
associatedtype D: BetweenTargetsProto
1068+
associatedtype E: AtDeploymentTargetProto
10871069
associatedtype F: AfterDeploymentTargetProto // expected-error {{'AfterDeploymentTargetProto' is only available in}}
10881070
associatedtype G: UnavailableProto
10891071
}
10901072

10911073
@_spi(Private)
1092-
public protocol SPINoAvailableProtoWithAssoc { // expected-note 3 {{add @available attribute to enclosing protocol}}
1074+
public protocol SPINoAvailableProtoWithAssoc { // expected-note 1 {{add @available attribute to enclosing protocol}}
10931075
associatedtype A: NoAvailableProto
10941076
associatedtype B: BeforeInliningTargetProto
10951077
associatedtype C: AtInliningTargetProto
1096-
// FIXME: Next two should be accepted (SPI)
1097-
associatedtype D: BetweenTargetsProto // expected-error {{'BetweenTargetsProto' is only available in}}
1098-
associatedtype E: AtDeploymentTargetProto // expected-error {{'AtDeploymentTargetProto' is only available in}}
1078+
associatedtype D: BetweenTargetsProto
1079+
associatedtype E: AtDeploymentTargetProto
10991080
associatedtype F: AfterDeploymentTargetProto // expected-error {{'AfterDeploymentTargetProto' is only available in}}
11001081
}
11011082

@@ -1118,21 +1099,19 @@ public enum UnavailableEnumWithTypeAliases {
11181099
public typealias A = NoAvailable
11191100
public typealias B = BeforeInliningTarget
11201101
public typealias C = AtInliningTarget
1121-
// FIXME: Next two should be accepted for compatibility (unavailable)
1122-
public typealias D = BetweenTargets // expected-error {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
1123-
public typealias E = AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
1102+
public typealias D = BetweenTargets
1103+
public typealias E = AtDeploymentTarget
11241104
public typealias F = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
11251105
public typealias G = Unavailable
11261106
}
11271107

11281108
@_spi(Private)
1129-
public enum SPIEnumWithTypeAliases { // expected-note 3 {{add @available attribute to enclosing enum}}
1109+
public enum SPIEnumWithTypeAliases { // expected-note 1 {{add @available attribute to enclosing enum}}
11301110
public typealias A = NoAvailable
11311111
public typealias B = BeforeInliningTarget
11321112
public typealias C = AtInliningTarget
1133-
// FIXME: Next two should be accepted (SPI)
1134-
public typealias D = BetweenTargets // expected-error {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
1135-
public typealias E = AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
1113+
public typealias D = BetweenTargets
1114+
public typealias E = AtDeploymentTarget
11361115
public typealias F = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing type alias}}
11371116
}
11381117

0 commit comments

Comments
 (0)