Skip to content

Sema: Fix isExported() for extension decls #42585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,33 @@ bool swift::isExported(const ValueDecl *VD) {
return false;
}

bool swift::isExported(const ExtensionDecl *ED) {
// An extension can only be exported if it extends an exported type.
if (auto *NTD = ED->getExtendedNominal()) {
if (!isExported(NTD))
return false;
}

// If there are any exported members then the extension is exported.
for (const Decl *D : ED->getMembers()) {
if (isExported(D))
return true;
}

// If the extension declares a conformance to a public protocol then the
// extension is exported.
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
for (const ProtocolDecl *PD : protocols) {
AccessScope scope =
PD->getFormalAccessScope(/*useDC*/nullptr,
/*treatUsableFromInlineAsPublic*/true);
if (scope.isPublic())
return true;
}

return false;
}

bool swift::isExported(const Decl *D) {
if (auto *VD = dyn_cast<ValueDecl>(D)) {
return isExported(VD);
Expand All @@ -91,10 +118,7 @@ bool swift::isExported(const Decl *D) {
return false;
}
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
if (auto *NTD = ED->getExtendedNominal())
return isExported(NTD);

return false;
return isExported(ED);
}

return true;
Expand Down
5 changes: 3 additions & 2 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ class ExportContext {
Optional<ExportabilityReason> getExportabilityReason() const;
};

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

/// Diagnose uses of unavailable declarations in expressions.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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
// RUN: %FileCheck --strict-whitespace --check-prefix CHECK-%target-os %s < %t.dump

// REQUIRES: swift_stable_abi

// Verify that -target-min-inlining-version min implies the correct OS version
// for the target OS.

// CHECK-macosx: {{^}}(root versions=[10.10.0,+Inf)
// CHECK-ios: {{^}}(root versions=[8.0,+Inf)
// CHECK-tvos: {{^}}(root versions=[9.0,+Inf)
// CHECK-watchos: {{^}}(root versions=[2.0,+Inf)

// CHECK-macosx-NEXT: {{^}} (resilience_boundary versions=[10.15.0,+Inf) decl=foo()
// CHECK-ios-NEXT: {{^}} (resilience_boundary versions=[13.0.0,+Inf) decl=foo()
// CHECK-tvos-NEXT: {{^}} (resilience_boundary versions=[13.0.0,+Inf) decl=foo()
// CHECK-watchos-NEXT: {{^}} (resilience_boundary versions=[6.0.0,+Inf) decl=foo()
func foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This test is the same as availability_refinement_contexts_target_min_inlining.swift
// but with a different run invocation to specifically test macCatalyst because
// currently there are no bots that run the tests with macCatalyst as the
// target OS.

// 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
// RUN: %FileCheck --strict-whitespace %s < %t.dump

// REQUIRES: maccatalyst_support

// Verify that -target-min-inlining-version min implies 13.1 on macCatalyst.

// CHECK: {{^}}(root versions=[13.1,+Inf)
// CHECK-NEXT: {{^}} (resilience_boundary versions=[14.4.0,+Inf) decl=foo()
func foo() {}
164 changes: 153 additions & 11 deletions test/attr/attr_inlinable_available.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,42 @@ public struct AfterDeploymentTarget {
@usableFromInline internal init() {}
}

// MARK: - Internal functions

//
// Both the signature and the body of internal functions should be typechecked
// using the minimum deployment target.
//

internal func internalFn( // expected-note 3 {{add @available attribute to enclosing global function}}
_: NoAvailable,
_: BeforeInliningTarget,
_: AtInliningTarget,
_: BetweenTargets,
_: AtDeploymentTarget,
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}
) {
defer {
_ = AtDeploymentTarget()
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}
}
_ = NoAvailable()
_ = BeforeInliningTarget()
_ = AtInliningTarget()
_ = BetweenTargets()
_ = AtDeploymentTarget()
_ = AfterDeploymentTarget() // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add 'if #available'}}

if #available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) {
_ = AfterDeploymentTarget()
}
}

// MARK: - Resilient functions

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

public func deployedUseNoAvailable( // expected-note 5 {{add @available attribute}}
Expand Down Expand Up @@ -224,9 +255,11 @@ public func deployedUseAfterDeploymentTarget(
}


// MARK: - @inlinable functions

//
// Uses in inlinable functions are based on the minimum inlining target
// Both the bodies and signatures of inlinable functions need to be typechecked
// using the minimum inlining target.
//

@inlinable public func inlinedUseNoAvailable( // expected-note 8 {{add @available attribute}}
Expand Down Expand Up @@ -395,15 +428,8 @@ public func deployedUseAfterDeploymentTarget(
_ = AfterDeploymentTarget()
}

//
// Edge cases.
//

// Internal functions should use the minimum deployment target.

internal func fn() {
_ = AtDeploymentTarget()
}
// MARK: - @_alwaysEmitIntoClient functions

// @_alwaysEmitIntoClient acts like @inlinable.

Expand Down Expand Up @@ -437,6 +463,9 @@ internal func fn() {
}
}


// MARK: - @_backDeploy functions

// @_backDeploy acts like @inlinable.

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
Expand Down Expand Up @@ -471,6 +500,9 @@ public func backDeployedToInliningTarget(
}
}


// MARK: - Default arguments

// Default arguments act like @inlinable.

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


// MARK: - Extensions

//
// Extensions are externally visible if they extend a public type and (1) have
// public members or (2) declare a conformance to a public protocol. Externally
// visible extensions should be typechecked with the inlining target.
//

// OK, NoAvailable is always available, both internally and externally.
extension NoAvailable {}
extension NoAvailable {
public func publicFunc1() {}
}

// OK, no public members and BetweenTargets is always available internally.
extension BetweenTargets {}

// OK, no public members and BetweenTargets is always available internally.
extension BetweenTargets {
internal func internalFunc1() {}
private func privateFunc1() {}
fileprivate func fileprivateFunc1() {}
}

// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension BetweenTargets {
public func publicFunc1() {}
}

// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension BetweenTargets {
@usableFromInline
internal func usableFromInlineFunc1() {}
}

// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension BetweenTargets {
internal func internalFunc2() {}
private func privateFunc2() {}
fileprivate func fileprivateFunc2() {}
public func publicFunc2() {}
}

// Same availability as BetweenTargets but internal instead of public.
@available(macOS 10.14.5, iOS 12.3, tvOS 12.3, watchOS 5.3, *)
internal struct BetweenTargetsInternal {}

// OK, extensions on internal types are never visible externally.
extension BetweenTargetsInternal {}
extension BetweenTargetsInternal {
public func publicFunc() {}
}

// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension AfterDeploymentTarget {}

// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension AfterDeploymentTarget {
internal func internalFunc1() {}
private func privateFunc1() {}
fileprivate func fileprivateFunc1() {}
}

// expected-error@+1 {{'AfterDeploymentTarget' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
extension AfterDeploymentTarget {
public func publicFunc1() {}
}


// MARK: Protocol conformances

internal protocol InternalProto {}

extension NoAvailable: InternalProto {}
extension BeforeInliningTarget: InternalProto {}
extension AtInliningTarget: InternalProto {}
extension BetweenTargets: InternalProto {}
extension AtDeploymentTarget: InternalProto {}
extension AfterDeploymentTarget: InternalProto {} // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}

public protocol PublicProto {}

extension NoAvailable: PublicProto {}
extension BeforeInliningTarget: PublicProto {}
extension AtInliningTarget: PublicProto {}
extension BetweenTargets: PublicProto {} // expected-error {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing extension}}
extension AtDeploymentTarget: PublicProto {} // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
extension AfterDeploymentTarget: PublicProto {} // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}


// MARK: - Type aliases

public typealias PublicNoAvailableAlias = NoAvailable
public typealias PublicBeforeInliningTargetAlias = BeforeInliningTarget
public typealias PublicAtInliningTargetAlias = AtInliningTarget
public typealias PublicBetweenTargetsAlias = BetweenTargets // expected-error {{'BetweenTargets' is only available in}}
public typealias PublicAtDeploymentTargetAlias = AtDeploymentTarget // expected-error {{'AtDeploymentTarget' is only available in}}
public typealias PublicAfterDeploymentTargetAlias = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}

typealias InternalNoAvailableAlias = NoAvailable
typealias InternalBeforeInliningTargetAlias = BeforeInliningTarget
typealias InternalAtInliningTargetAlias = AtInliningTarget
typealias InternalBetweenTargetsAlias = BetweenTargets
typealias InternalAtDeploymentTargetAlias = AtDeploymentTarget
typealias InternalAfterDeploymentTargetAlias = AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in}}


// MARK: - Top-level code

// Top-level code, if somehow present in a resilient module, is treated like
// a non-inlinable function.
defer {
Expand Down
Loading