diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 66d57b27aaedc..0da7bf21c9cfe 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -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 diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 7b6774729a8e0..611edf3a5d769 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -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(useFile); + return !useSF || useSF->isImportedAsSPI(VD); + } return true; } + } llvm_unreachable("bad access level"); } diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index f18edb364e3ae..cdc5f2d3472e7 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -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) { diff --git a/test/SPI/Inputs/spi_helper.swift b/test/SPI/Inputs/spi_helper.swift index 506ddd83ccb37..2eef695f7efe7 100644 --- a/test/SPI/Inputs/spi_helper.swift +++ b/test/SPI/Inputs/spi_helper.swift @@ -4,6 +4,10 @@ public protocol PublicProto { associatedtype Assoc } +public struct PublicType { + public init() { } +} + public func publicFunc() { print("publicFunc") } func internalFunc() {} @@ -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 } diff --git a/test/SPI/local_spi_decls.swift b/test/SPI/local_spi_decls.swift index d9350b4b3aa46..5f36727691912 100644 --- a/test/SPI/local_spi_decls.swift +++ b/test/SPI/local_spi_decls.swift @@ -115,11 +115,20 @@ 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) @@ -127,8 +136,11 @@ public func inlinablePublic() { 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 diff --git a/test/SPI/private_swiftinterface.swift b/test/SPI/private_swiftinterface.swift index 83d1b83e269cc..89c00474ba856 100644 --- a/test/SPI/private_swiftinterface.swift +++ b/test/SPI/private_swiftinterface.swift @@ -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 +// 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 @@ -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) diff --git a/test/SPI/public_client.swift b/test/SPI/public_client.swift index 7c59617e4c02e..c9e994ab06e83 100644 --- a/test/SPI/public_client.swift +++ b/test/SPI/public_client.swift @@ -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 @@ -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}} diff --git a/test/SPI/spi_client.swift b/test/SPI/spi_client.swift index 574ff2569251f..9046af2384d36 100644 --- a/test/SPI/spi_client.swift +++ b/test/SPI/spi_client.swift @@ -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