Skip to content
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
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ EXPERIMENTAL_FEATURE(CheckImplementationOnly, true)
/// report references to implementation-only imported modules.
EXPERIMENTAL_FEATURE(CheckImplementationOnlyStrict, false)

/// Check that use sites have the required @_spi import for operators.
EXPERIMENTAL_FEATURE(EnforceSPIOperatorGroup, true)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
12 changes: 11 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5536,9 +5536,19 @@ static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
return srcPkg && usePkg && usePkg->isSamePackageAs(srcPkg);
}
case AccessLevel::Public:
case AccessLevel::Open:
case AccessLevel::Open: {
if (VD->getASTContext().LangOpts.hasFeature(
Feature::EnforceSPIOperatorGroup) &&
VD->isOperator() && VD->isSPI()) {
const DeclContext *useFile = useDC->getModuleScopeContext();
if (useFile->getParentModule() == sourceDC->getParentModule())
return true;
auto *useSF = dyn_cast<SourceFile>(useFile);
return !useSF || useSF->isImportedAsSPI(VD);
}
return true;
}
}
llvm_unreachable("bad access level");
}

Expand Down
1 change: 1 addition & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ UNINTERESTING_FEATURE(SameElementRequirements)
UNINTERESTING_FEATURE(SendingArgsAndResults)
UNINTERESTING_FEATURE(CheckImplementationOnly)
UNINTERESTING_FEATURE(CheckImplementationOnlyStrict)
UNINTERESTING_FEATURE(EnforceSPIOperatorGroup)

static bool findUnderscoredLifetimeAttr(Decl *decl) {
auto hasUnderscoredLifetimeAttr = [](Decl *decl) {
Expand Down
7 changes: 7 additions & 0 deletions test/SPI/Inputs/spi_helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ public protocol PublicProto {
associatedtype Assoc
}

public struct PublicType {
public init() { }
}

public func publicFunc() { print("publicFunc") }

func internalFunc() {}
Expand Down Expand Up @@ -94,3 +98,6 @@ public enum PublicEnum {
case publicCase
@_spi(HelperSPI) case spiCase
}

@_spi(HelperSPI) public func -(_ s1: PublicType, _ s2: PublicType) -> PublicType { s1 }
@_spi(HelperSPI) public func +(_ s1: PublicType, _ s2: PublicType) -> PublicType { s1 }
12 changes: 12 additions & 0 deletions test/SPI/local_spi_decls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,32 @@ public func publicFuncWithDefaultValue(_ p: SPIClass = SPIClass()) {} // expecte
@_spi(S)
public func spiFuncWithDefaultValue(_ p: SPIClass = SPIClass()) {}

public struct PublicType {
public init() { }
}
@_spi(S) public func -(_ s1: PublicType, _ s2: PublicType) -> PublicType { s1 }

public let o1 = PublicType()
public let o2 = PublicType()

@inlinable
public func inlinablePublic() {
spiFunc() // expected-error {{global function 'spiFunc()' cannot be used in an '@inlinable' function because it is SPI}}
let _ = SPIClass() // expected-error {{class 'SPIClass' cannot be used in an '@inlinable' function because it is SPI}}
// expected-error@-1 {{initializer 'init()' cannot be used in an '@inlinable' function because it is SPI}}
let _ = o1 - o2 // expected-error {{operator function '-' cannot be used in an '@inlinable' function because it is SPI}}
}

@_spi(S)
@inlinable
public func inlinableSPI() {
spiFunc()
let _ = SPIClass()
let _ = o1 - o2
}

@_spi(S) func internalFunc() {} // expected-error {{internal global function cannot be declared '@_spi' because only public and open declarations can be '@_spi'}}

@_spi(S) package func packageFunc() {} // expected-error {{package global function cannot be declared '@_spi' because only public and open declarations can be '@_spi'}}

let _ = o1 - o2
13 changes: 9 additions & 4 deletions test/SPI/private_swiftinterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@

/// 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 -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: cat %t/Main.swiftinterface | %FileCheck -check-prefix=CHECK-PUBLIC %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use cat instead of < here, out of curiosity? FWIW, switching to --input-file instead would improve debugging somewhat (that way FileCheck will print the path to the file when there are mismatches)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was it, I prefer that style now to see the name of the file in the outputs. I'll take a look at --input-file, maybe it will be worth switching style again.

// RUN: cat %t/Main.private.swiftinterface | %FileCheck -check-prefix=CHECK-PRIVATE %s
// 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

/// Both the public and private textual interfaces should have
/// SPI information with `-library-level spi`.
// RUN: %target-swift-frontend -typecheck %s -emit-module-interface-path %t/SPIModule.swiftinterface -emit-private-module-interface-path %t/SPIModule.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t -module-name SPIModule -library-level spi
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/SPIModule.swiftinterface
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/SPIModule.private.swiftinterface
// RUN: cat %t/SPIModule.swiftinterface | %FileCheck -check-prefix=CHECK-PRIVATE %s
// RUN: cat %t/SPIModule.private.swiftinterface | %FileCheck -check-prefix=CHECK-PRIVATE %s
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/SPIModule.swiftinterface
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/SPIModule.private.swiftinterface -module-name SPIModule

Expand Down Expand Up @@ -238,6 +238,11 @@ extension IOIPublicStruct : LocalPublicProto {}
// CHECK-PRIVATE: @_spi(S) private var spiTypeInFrozen1
}

public struct OpType {}
@_spi(S) public func +(_ s1: OpType, _ s2: OpType) -> OpType { s1 }
// CHECK-PRIVATE: @_spi(S) public func + (s1: {{.*}}.OpType, s2: {{.*}}.OpType) -> {{.*}}.OpType
// CHECK-PUBLIC-NOT: func +

// The dummy conformance should be only in the private swiftinterface for
// SPI extensions.
@_spi(LocalSPI)
Expand Down
16 changes: 13 additions & 3 deletions test/SPI/public_client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
// RUN: %target-swift-frontend -emit-module %S/Inputs/spi_helper.swift -module-name SPIHelper -emit-module-path %t/SPIHelper.swiftmodule -emit-module-interface-path %t/SPIHelper.swiftinterface -emit-private-module-interface-path %t/SPIHelper.private.swiftinterface -enable-library-evolution -swift-version 5 -parse-as-library

/// Reading from swiftmodule
// RUN: %target-typecheck-verify-swift -verify-ignore-unrelated -I %t -verify-ignore-unknown
// RUN: %target-typecheck-verify-swift -verify-ignore-unrelated -I %t -verify-ignore-unknown \
// RUN: -enable-experimental-feature EnforceSPIOperatorGroup

/// Reading from .private.swiftinterface
// RUN: rm %t/SPIHelper.swiftmodule
// RUN: %target-typecheck-verify-swift -verify-ignore-unrelated -I %t -verify-ignore-unknown
// RUN: %target-typecheck-verify-swift -verify-ignore-unrelated -I %t -verify-ignore-unknown \
// RUN: -enable-experimental-feature EnforceSPIOperatorGroup

/// Reading from the public .swiftinterface should raise errors on missing
/// declarations.
// RUN: rm %t/SPIHelper.private.swiftinterface
// RUN: not %target-swift-frontend -typecheck -I %t %s

// REQUIRES: swift_feature_EnforceSPIOperatorGroup

import SPIHelper

// Use the public API
Expand All @@ -40,8 +44,14 @@ otherApiFunc() // expected-error {{cannot find 'otherApiFunc' in scope}}
public func publicUseOfSPI(param: SPIClass) -> SPIClass {} // expected-error 2{{cannot find type 'SPIClass' in scope}}
public func publicUseOfSPI2() -> [SPIClass] {} // expected-error {{cannot find type 'SPIClass' in scope}}

public let o1 = PublicType()
public let o2 = PublicType()

@inlinable
func inlinable() -> SPIClass { // expected-error {{cannot find type 'SPIClass' in scope}}
public func inlinable() -> SPIClass { // expected-error {{cannot find type 'SPIClass' in scope}}
spiFunc() // expected-error {{cannot find 'spiFunc' in scope}}
_ = SPIClass() // expected-error {{cannot find 'SPIClass' in scope}}
let _ = o1 - o2 // expected-error {{binary operator '-' cannot be applied to two 'PublicType' operands}}
}

let _ = o1 - o2 // expected-error {{binary operator '-' cannot be applied to two 'PublicType' operands}}
6 changes: 6 additions & 0 deletions test/SPI/spi_client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ public func inlinable1() -> SPIClass { // expected-error {{class 'SPIClass' cann
_ = [SPIClass]() // expected-error {{class 'SPIClass' cannot be used in an '@inlinable' function because it is an SPI imported from 'SPIHelper'}}
}

public let o1 = PublicType()
public let o2 = PublicType()

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

let _ = o1 - o2