Skip to content

Commit 00ac799

Browse files
committed
Sema: Fix isExported() for extension decls in order to correct availability diagnostics when -target-min-inlining-version min is specified.
Previously, an extension decl was always considered exported (externally visible to module clients) as long as it extended an exported type. Extensions need to either contain some externally visible member (e.g. a public method) or implement a conformance to a public protcol, though, to actually be exported. Without this fix, the compiler incorrectly requires internal extensions to types that are always available at runtime to have declared availability which would be a nuisance for library authors. As part of testing this change, I expanded the attr_inlinable_available.swift test case significantly and that prompted me to scrap the copy of the test specific to macCatalyst as it seemed like needing to keep the two tests in sync was going to be a liability going forward. I replaced the deleted test with a couple of more targeted tests that use `-dump-type-refinement-contexts` to verify the effect of `-target-min-inlining-version min` on the root refinement context. Again a macCatalyst version of the test is required because we don't have bots that are configured to make the macCatalyst runtime the "target" OS. Resolves rdar://91382040
1 parent 67af3a1 commit 00ac799

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)