Skip to content

Commit b806113

Browse files
authored
Merge pull request #42585 from tshortli/target-min-inlining-version-extension-availability
Sema: Fix `isExported()` for extension decls
2 parents 0516f15 + 00ac799 commit b806113

6 files changed

+217
-584
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,33 @@ bool swift::isExported(const ValueDecl *VD) {
7878
return false;
7979
}
8080

81+
bool swift::isExported(const ExtensionDecl *ED) {
82+
// An extension can only be exported if it extends an exported type.
83+
if (auto *NTD = ED->getExtendedNominal()) {
84+
if (!isExported(NTD))
85+
return false;
86+
}
87+
88+
// If there are any exported members then the extension is exported.
89+
for (const Decl *D : ED->getMembers()) {
90+
if (isExported(D))
91+
return true;
92+
}
93+
94+
// If the extension declares a conformance to a public protocol then the
95+
// extension is exported.
96+
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
97+
for (const ProtocolDecl *PD : protocols) {
98+
AccessScope scope =
99+
PD->getFormalAccessScope(/*useDC*/nullptr,
100+
/*treatUsableFromInlineAsPublic*/true);
101+
if (scope.isPublic())
102+
return true;
103+
}
104+
105+
return false;
106+
}
107+
81108
bool swift::isExported(const Decl *D) {
82109
if (auto *VD = dyn_cast<ValueDecl>(D)) {
83110
return isExported(VD);
@@ -91,10 +118,7 @@ bool swift::isExported(const Decl *D) {
91118
return false;
92119
}
93120
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
94-
if (auto *NTD = ED->getExtendedNominal())
95-
return isExported(NTD);
96-
97-
return false;
121+
return isExported(ED);
98122
}
99123

100124
return true;

lib/Sema/TypeCheckAvailability.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,10 @@ class ExportContext {
183183
Optional<ExportabilityReason> getExportabilityReason() const;
184184
};
185185

186-
/// Check if a public declaration is part of a module's API; that is, this
187-
/// will return false if the declaration is @_spi or @_implementationOnly.
186+
/// Check if a declaration is exported as part of a module's external interface.
187+
/// This includes public and @usableFromInline decls.
188188
bool isExported(const ValueDecl *VD);
189+
bool isExported(const ExtensionDecl *ED);
189190
bool isExported(const Decl *D);
190191

191192
/// Diagnose uses of unavailable declarations in expressions.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -target %target-next-stable-abi-triple -typecheck -dump-type-refinement-contexts -target-min-inlining-version min %s > %t.dump 2>&1
2+
// RUN: %FileCheck --strict-whitespace --check-prefix CHECK-%target-os %s < %t.dump
3+
4+
// REQUIRES: swift_stable_abi
5+
6+
// Verify that -target-min-inlining-version min implies the correct OS version
7+
// for the target OS.
8+
9+
// CHECK-macosx: {{^}}(root versions=[10.10.0,+Inf)
10+
// CHECK-ios: {{^}}(root versions=[8.0,+Inf)
11+
// CHECK-tvos: {{^}}(root versions=[9.0,+Inf)
12+
// CHECK-watchos: {{^}}(root versions=[2.0,+Inf)
13+
14+
// CHECK-macosx-NEXT: {{^}} (resilience_boundary versions=[10.15.0,+Inf) decl=foo()
15+
// CHECK-ios-NEXT: {{^}} (resilience_boundary versions=[13.0.0,+Inf) decl=foo()
16+
// CHECK-tvos-NEXT: {{^}} (resilience_boundary versions=[13.0.0,+Inf) decl=foo()
17+
// CHECK-watchos-NEXT: {{^}} (resilience_boundary versions=[6.0.0,+Inf) decl=foo()
18+
func foo() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This test is the same as availability_refinement_contexts_target_min_inlining.swift
2+
// but with a different run invocation to specifically test macCatalyst because
3+
// currently there are no bots that run the tests with macCatalyst as the
4+
// target OS.
5+
6+
// RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -target %target-cpu-apple-ios14.4-macabi -typecheck -dump-type-refinement-contexts -target-min-inlining-version min %s > %t.dump 2>&1
7+
// RUN: %FileCheck --strict-whitespace %s < %t.dump
8+
9+
// REQUIRES: maccatalyst_support
10+
11+
// Verify that -target-min-inlining-version min implies 13.1 on macCatalyst.
12+
13+
// CHECK: {{^}}(root versions=[13.1,+Inf)
14+
// CHECK-NEXT: {{^}} (resilience_boundary versions=[14.4.0,+Inf) decl=foo()
15+
func foo() {}

test/attr/attr_inlinable_available.swift

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,42 @@ public struct AfterDeploymentTarget {
7171
@usableFromInline internal init() {}
7272
}
7373

74+
// MARK: - Internal functions
7475

76+
//
77+
// Both the signature and the body of internal functions should be typechecked
78+
// using the minimum deployment target.
79+
//
80+
81+
internal func internalFn( // expected-note 3 {{add @available attribute to enclosing global function}}
82+
_: NoAvailable,
83+
_: BeforeInliningTarget,
84+
_: AtInliningTarget,
85+
_: BetweenTargets,
86+
_: AtDeploymentTarget,
87+
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
88+
) {
89+
defer {
90+
_ = AtDeploymentTarget()
91+
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
92+
}
93+
_ = NoAvailable()
94+
_ = BeforeInliningTarget()
95+
_ = AtInliningTarget()
96+
_ = BetweenTargets()
97+
_ = AtDeploymentTarget()
98+
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
99+
100+
if #available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) {
101+
_ = AfterDeploymentTarget()
102+
}
103+
}
104+
105+
// MARK: - Resilient functions
75106

76107
//
77-
// Uses in resilient functions are based on the minimum deployment target
78-
// (i.e. the -target).
108+
// The body of a resilient function is typechecked using the minimum deployment
109+
// but the function's signature should be checked with the inlining target.
79110
//
80111

81112
public func deployedUseNoAvailable( // expected-note 5 {{add @available attribute}}
@@ -224,9 +255,11 @@ public func deployedUseAfterDeploymentTarget(
224255
}
225256

226257

258+
// MARK: - @inlinable functions
227259

228260
//
229-
// Uses in inlinable functions are based on the minimum inlining target
261+
// Both the bodies and signatures of inlinable functions need to be typechecked
262+
// using the minimum inlining target.
230263
//
231264

232265
@inlinable public func inlinedUseNoAvailable( // expected-note 8 {{add @available attribute}}
@@ -395,15 +428,8 @@ public func deployedUseAfterDeploymentTarget(
395428
_ = AfterDeploymentTarget()
396429
}
397430

398-
//
399-
// Edge cases.
400-
//
401-
402-
// Internal functions should use the minimum deployment target.
403431

404-
internal func fn() {
405-
_ = AtDeploymentTarget()
406-
}
432+
// MARK: - @_alwaysEmitIntoClient functions
407433

408434
// @_alwaysEmitIntoClient acts like @inlinable.
409435

@@ -437,6 +463,9 @@ internal func fn() {
437463
}
438464
}
439465

466+
467+
// MARK: - @_backDeploy functions
468+
440469
// @_backDeploy acts like @inlinable.
441470

442471
@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
@@ -471,6 +500,9 @@ public func backDeployedToInliningTarget(
471500
}
472501
}
473502

503+
504+
// MARK: - Default arguments
505+
474506
// Default arguments act like @inlinable.
475507

476508
public func defaultArgsUseNoAvailable( // expected-note 3 {{add @available attribute}}
@@ -551,6 +583,116 @@ internal struct InternalStruct { // expected-note {{add @available attribute}}
551583
var fInternal: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
552584
}
553585

586+
587+
// MARK: - Extensions
588+
589+
//
590+
// Extensions are externally visible if they extend a public type and (1) have
591+
// public members or (2) declare a conformance to a public protocol. Externally
592+
// visible extensions should be typechecked with the inlining target.
593+
//
594+
595+
// OK, NoAvailable is always available, both internally and externally.
596+
extension NoAvailable {}
597+
extension NoAvailable {
598+
public func publicFunc1() {}
599+
}
600+
601+
// OK, no public members and BetweenTargets is always available internally.
602+
extension BetweenTargets {}
603+
604+
// OK, no public members and BetweenTargets is always available internally.
605+
extension BetweenTargets {
606+
internal func internalFunc1() {}
607+
private func privateFunc1() {}
608+
fileprivate func fileprivateFunc1() {}
609+
}
610+
611+
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
612+
extension BetweenTargets {
613+
public func publicFunc1() {}
614+
}
615+
616+
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
617+
extension BetweenTargets {
618+
@usableFromInline
619+
internal func usableFromInlineFunc1() {}
620+
}
621+
622+
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
623+
extension BetweenTargets {
624+
internal func internalFunc2() {}
625+
private func privateFunc2() {}
626+
fileprivate func fileprivateFunc2() {}
627+
public func publicFunc2() {}
628+
}
629+
630+
// Same availability as BetweenTargets but internal instead of public.
631+
@available(macOS 10.14.5, iOS 12.3, tvOS 12.3, watchOS 5.3, *)
632+
internal struct BetweenTargetsInternal {}
633+
634+
// OK, extensions on internal types are never visible externally.
635+
extension BetweenTargetsInternal {}
636+
extension BetweenTargetsInternal {
637+
public func publicFunc() {}
638+
}
639+
640+
// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
641+
extension AfterDeploymentTarget {}
642+
643+
// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
644+
extension AfterDeploymentTarget {
645+
internal func internalFunc1() {}
646+
private func privateFunc1() {}
647+
fileprivate func fileprivateFunc1() {}
648+
}
649+
650+
// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
651+
extension AfterDeploymentTarget {
652+
public func publicFunc1() {}
653+
}
654+
655+
656+
// MARK: Protocol conformances
657+
658+
internal protocol InternalProto {}
659+
660+
extension NoAvailable: InternalProto {}
661+
extension BeforeInliningTarget: InternalProto {}
662+
extension AtInliningTarget: InternalProto {}
663+
extension BetweenTargets: InternalProto {}
664+
extension AtDeploymentTarget: InternalProto {}
665+
extension AfterDeploymentTarget: InternalProto {} // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
666+
667+
public protocol PublicProto {}
668+
669+
extension NoAvailable: PublicProto {}
670+
extension BeforeInliningTarget: PublicProto {}
671+
extension AtInliningTarget: PublicProto {}
672+
extension BetweenTargets: PublicProto {} // expected-error {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing extension}}
673+
extension AtDeploymentTarget: PublicProto {} // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
674+
extension AfterDeploymentTarget: PublicProto {} // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
675+
676+
677+
// MARK: - Type aliases
678+
679+
public typealias PublicNoAvailableAlias = NoAvailable
680+
public typealias PublicBeforeInliningTargetAlias = BeforeInliningTarget
681+
public typealias PublicAtInliningTargetAlias = AtInliningTarget
682+
public typealias PublicBetweenTargetsAlias = BetweenTargets // expected-error {{'BetweenTargets' is only available in}}
683+
public typealias PublicAtDeploymentTargetAlias = AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}}
684+
public typealias PublicAfterDeploymentTargetAlias = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
685+
686+
typealias InternalNoAvailableAlias = NoAvailable
687+
typealias InternalBeforeInliningTargetAlias = BeforeInliningTarget
688+
typealias InternalAtInliningTargetAlias = AtInliningTarget
689+
typealias InternalBetweenTargetsAlias = BetweenTargets
690+
typealias InternalAtDeploymentTargetAlias = AtDeploymentTarget
691+
typealias InternalAfterDeploymentTargetAlias = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
692+
693+
694+
// MARK: - Top-level code
695+
554696
// Top-level code, if somehow present in a resilient module, is treated like
555697
// a non-inlinable function.
556698
defer {

0 commit comments

Comments
 (0)