Skip to content

[Sema] Accept the use of SPI in SPI inlinable code #33970

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
merged 4 commits into from
Sep 17, 2020
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
2 changes: 2 additions & 0 deletions lib/Frontend/ModuleInterfaceSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ class InheritedProtocolCollector {
return false;
assert(nominal->isGenericContext());

if (printOptions.PrintSPIs)
out << "@_spi(" << DummyProtocolName << ")\n";
out << "@available(*, unavailable)\nextension ";
nominal->getDeclaredType().print(out, printOptions);
out << " : ";
Expand Down
15 changes: 8 additions & 7 deletions lib/Sema/ResilienceDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
if (D->getDeclContext()->isLocalContext())
return false;

// Public non-SPI declarations are OK.
// Public declarations or SPI used from SPI are OK.
if (D->getFormalAccessScope(/*useDC=*/nullptr,
Kind.allowUsableFromInline).isPublic() &&
!D->isSPI())
!(D->isSPI() && !DC->getInnermostDeclarationDeclContext()->isSPI()))
return false;

auto &Context = DC->getASTContext();
Expand Down Expand Up @@ -149,14 +149,15 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,

static bool diagnoseDeclExportability(SourceLoc loc, const ValueDecl *D,
const SourceFile &userSF,
const DeclContext *userDC,
FragileFunctionKind fragileKind) {
assert(fragileKind.kind != FragileFunctionKind::None);

auto definingModule = D->getModuleContext();

bool isImplementationOnly =
userSF.isImportedImplementationOnly(definingModule);
if (!isImplementationOnly && !userSF.isImportedAsSPI(D))
auto originKind = getDisallowedOriginKind(
D, userSF, userDC->getInnermostDeclarationDeclContext());
if (originKind == DisallowedOriginKind::None)
return false;

// TODO: different diagnostics
Expand All @@ -165,7 +166,7 @@ static bool diagnoseDeclExportability(SourceLoc loc, const ValueDecl *D,
D->getDescriptiveKind(), D->getName(),
static_cast<unsigned>(fragileKind.kind),
definingModule->getName(),
static_cast<unsigned>(!isImplementationOnly));
static_cast<unsigned>(originKind));
return true;
}

Expand Down Expand Up @@ -234,7 +235,7 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
return false;

const ValueDecl *D = declRef.getDecl();
if (diagnoseDeclExportability(loc, D, *userSF, fragileKind))
if (diagnoseDeclExportability(loc, D, *userSF, DC, fragileKind))
return true;
if (diagnoseGenericArgumentsExportability(loc, declRef.getSubstitutions(),
*userSF, DC)) {
Expand Down
8 changes: 6 additions & 2 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,10 @@ void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) {
if (auto VD = dyn_cast<ValueDecl>(D)) {
// VD must be public or open to use an @_spi attribute.
auto declAccess = VD->getFormalAccess();
auto DC = VD->getDeclContext()->getAsDecl();
if (declAccess < AccessLevel::Public &&
!VD->getAttrs().hasAttribute<UsableFromInlineAttr>()) {
!VD->getAttrs().hasAttribute<UsableFromInlineAttr>() &&
!(DC && DC->isSPI())) {
diagnoseAndRemoveAttr(attr,
diag::spi_attribute_on_non_public,
declAccess,
Expand All @@ -896,7 +898,9 @@ void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) {
// Forbid stored properties marked SPI in frozen types.
if (auto property = dyn_cast<AbstractStorageDecl>(VD))
if (auto DC = dyn_cast<NominalTypeDecl>(D->getDeclContext()))
if (property->hasStorage() && !DC->isFormallyResilient())
if (property->hasStorage() &&
!DC->isFormallyResilient() &&
!DC->isSPI())
diagnoseAndRemoveAttr(attr,
diag::spi_attribute_on_frozen_stored_properties,
VD->getName());
Expand Down
44 changes: 39 additions & 5 deletions test/SPI/local_spi_decls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
// RUN: %target-typecheck-verify-swift -I %t -verify-ignore-unknown -swift-version 5

// SPI declarations
@_spi(MySPI) public func spiFunc() {} // expected-note {{global function 'spiFunc()' is not '@usableFromInline' or public}}
@_spi(MySPI) public func spiFunc() {} // expected-note 2 {{global function 'spiFunc()' is not '@usableFromInline' or public}}
@_spi(+) public func invalidSPIName() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
@_spi(🤔) public func emojiNamedSPI() {}
@_spi() public func emptyParensSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
@_spi(set) public func keywordSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}

@_spi(S) public class SPIClass {} // expected-note 3 {{type declared here}}
// expected-note @-1 2 {{class 'SPIClass' is not '@usableFromInline' or public}}
@_spi(S) public class SPIClass { // expected-note 5 {{type declared here}}
// expected-note @-1 3 {{class 'SPIClass' is not '@usableFromInline' or public}}
// expected-note @-2 {{class 'SPIClass' is not public}}
public init() {}
}
class InternalClass {} // expected-note 2 {{type declared here}}
private class PrivateClass {} // expected-note 2 {{type declared here}}

Expand All @@ -31,13 +34,25 @@ func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' a
_ = SPIClass() // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
}

@_spi(S) public struct SPIStruct {} // expected-note 2 {{struct 'SPIStruct' is not '@usableFromInline' or public}}
@_spi(S) public struct SPIStruct { // expected-note 2 {{struct 'SPIStruct' is not '@usableFromInline' or public}}
public init() {}
}

@frozen public struct FrozenStruct {
@_spi(S) public var spiInFrozen = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
// expected-error @-1 {{stored property 'spiInFrozen' cannot be declared '@_spi' in a '@frozen' struct}}

var asdf = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
var spiTypeInFrozen = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
private var spiTypeInFrozen1: SPIClass // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
}

@_spi(S)
@frozen public struct SPIFrozenStruct {
var spiTypeInFrozen = SPIStruct()
private var spiTypeInFrozen1: SPIClass

@_spi(S)
private var privateSPIInFrozenSPI = SPIStruct()
}

private protocol PrivateProtocol {} // expected-note {{type declared here}}
Expand Down Expand Up @@ -81,3 +96,22 @@ public struct NestedParent {
public struct Nested { }
let nested: Nested
}

public func publicFuncWithDefaultValue(_ p: SPIClass = SPIClass()) {} // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
// expected-error @-1 {{class 'SPIClass' is '@_spi' and cannot be referenced from a default argument value}}

@_spi(S)
public func spiFuncWithDefaultValue(_ p: SPIClass = SPIClass()) {}

@inlinable
public func inlinablePublic() {
spiFunc() // expected-error {{global function 'spiFunc()' is '@_spi' and cannot be referenced from an '@inlinable' function}}
let _ = SPIClass() // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
}

@_spi(S)
@inlinable
public func inlinableSPI() {
spiFunc()
let _ = SPIClass()
}
30 changes: 23 additions & 7 deletions test/SPI/private_swiftinterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
// RUN: %target-swift-frontend -emit-module %t/SPIHelper.swiftinterface -emit-module-path %t/SPIHelper-from-public-swiftinterface.swiftmodule -swift-version 5 -module-name SPIHelper -enable-library-evolution

/// Test the textual interfaces generated from this test.
// RUN: %target-swift-frontend -typecheck %s -emit-module-interface-path %t/main.swiftinterface -emit-private-module-interface-path %t/main.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/main.swiftinterface
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/main.private.swiftinterface
// RUN: %target-swift-frontend -typecheck %s -emit-module-interface-path %t/Main.swiftinterface -emit-private-module-interface-path %t/Main.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t -module-name Main
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/Main.swiftinterface
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/Main.private.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Main.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Main.private.swiftinterface -module-name Main

/// Serialize and deserialize this module, then print.
// RUN: %target-swift-frontend -emit-module %s -emit-module-path %t/merged-partial.swiftmodule -swift-version 5 -I %t -module-name merged -enable-library-evolution
// RUN: %target-swift-frontend -merge-modules %t/merged-partial.swiftmodule -module-name merged -emit-module -emit-module-path %t/merged.swiftmodule -I %t -emit-module-interface-path %t/merged.swiftinterface -emit-private-module-interface-path %t/merged.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/merged.swiftinterface
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/merged.private.swiftinterface
// RUN: %target-swift-frontend -emit-module %s -emit-module-path %t/Merged-partial.swiftmodule -swift-version 5 -I %t -module-name Merged -enable-library-evolution
// RUN: %target-swift-frontend -merge-modules %t/Merged-partial.swiftmodule -module-name Merged -emit-module -emit-module-path %t/Merged.swiftmodule -I %t -emit-module-interface-path %t/Merged.swiftinterface -emit-private-module-interface-path %t/Merged.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/Merged.swiftinterface
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/Merged.private.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Merged.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Merged.private.swiftinterface -module-name Merged

@_spi(HelperSPI) @_spi(OtherSPI) @_spi(OtherSPI) import SPIHelper
// CHECK-PUBLIC: import SPIHelper
Expand Down Expand Up @@ -162,6 +166,18 @@ extension IOIPublicStruct : LocalPublicProto {}
// CHECK-PRIVATE-NOT: IOIPublicStruct
// CHECK-PUBLIC-NOT: IOIPublicStruct

@_spi(S)
@frozen public struct SPIFrozenStruct {
// CHECK-PRIVATE: struct SPIFrozenStruct
// CHECK-PUBLIC-NOT: SPIFrozenStruct

var spiTypeInFrozen = SPIStruct()
// CHECK-PRIVATE: @_spi(S) internal var spiTypeInFrozen

private var spiTypeInFrozen1: SPIClass
// CHECK-PRIVATE: @_spi(S) private var spiTypeInFrozen1
}

// The dummy conformance should be only in the private swiftinterface for
// SPI extensions.
@_spi(LocalSPI)
Expand Down
10 changes: 9 additions & 1 deletion test/SPI/spi_client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,16 @@ public func publicUseOfSPI(param: SPIClass) -> SPIClass {} // expected-error 2{{
public func publicUseOfSPI2() -> [SPIClass] {} // expected-error {{cannot use class 'SPIClass' here; it is an SPI imported from 'SPIHelper'}}

@inlinable
func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
public func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
spiFunc() // expected-error {{global function 'spiFunc()' is '@_spi' and cannot be referenced from an '@inlinable' function}}
_ = SPIClass() // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
_ = [SPIClass]() // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
}

@_spi(S)
@inlinable
public func inlinable() -> SPIClass {
spiFunc()
_ = SPIClass()
_ = [SPIClass]()
}