diff --git a/include/swift/Frontend/ModuleInterfaceSupport.h b/include/swift/Frontend/ModuleInterfaceSupport.h index 4dfff6a0f7d6d..18f4105e02a89 100644 --- a/include/swift/Frontend/ModuleInterfaceSupport.h +++ b/include/swift/Frontend/ModuleInterfaceSupport.h @@ -43,6 +43,10 @@ struct ModuleInterfaceOptions { // Print SPI decls and attributes. bool PrintSPIs = false; + + /// Print imports with both @_implementationOnly and @_spi, only applies + /// when PrintSPIs is true. + bool ExperimentalSPIImports = false; }; extern version::Version InterfaceFormatVersion; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 496caa77100ea..959636ea445ee 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -627,6 +627,10 @@ def module_interface_preserve_types_as_written : HelpText<"When emitting a module interface, preserve types as they were " "written in the source">; +def experimental_spi_imports : + Flag<["-"], "experimental-spi-imports">, + HelpText<"Enable experimental support for SPI imports">; + def experimental_print_full_convention : Flag<["-"], "experimental-print-full-convention">, HelpText<"When emitting a module interface, emit additional @convention " diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index e629322648553..97eb6b98bf5d1 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -289,6 +289,8 @@ static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts, Args.hasArg(OPT_module_interface_preserve_types_as_written); Opts.PrintFullConvention |= Args.hasArg(OPT_experimental_print_full_convention); + Opts.ExperimentalSPIImports |= + Args.hasArg(OPT_experimental_spi_imports); } /// Save a copy of any flags marked as ModuleInterfaceOption, if running diff --git a/lib/Frontend/ModuleInterfaceSupport.cpp b/lib/Frontend/ModuleInterfaceSupport.cpp index 91ef3e73a2744..4b5febcc60ff3 100644 --- a/lib/Frontend/ModuleInterfaceSupport.cpp +++ b/lib/Frontend/ModuleInterfaceSupport.cpp @@ -104,6 +104,21 @@ static void printImports(raw_ostream &out, allImportFilter |= ModuleDecl::ImportFilterKind::Private; allImportFilter |= ModuleDecl::ImportFilterKind::SPIAccessControl; + // With -experimental-spi-imports: + // When printing the private swiftinterface file, print implementation-only + // imports only if they are also SPI. First, list all implementation-only + // imports and filter them later. + llvm::SmallSet ioiImportSet; + if (Opts.PrintSPIs && Opts.ExperimentalSPIImports) { + allImportFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly; + + SmallVector ioiImport; + M->getImportedModules(ioiImport, + ModuleDecl::ImportFilterKind::ImplementationOnly); + ioiImportSet.insert(ioiImport.begin(), ioiImport.end()); + } + SmallVector allImports; M->getImportedModules(allImports, allImportFilter); ModuleDecl::removeDuplicateImports(allImports); @@ -124,13 +139,21 @@ static void printImports(raw_ostream &out, continue; } + llvm::SmallVector spis; + M->lookupImportedSPIGroups(importedModule, spis); + + // Only print implementation-only imports which have an SPI import. + if (ioiImportSet.count(import)) { + if (spis.empty()) + continue; + out << "@_implementationOnly "; + } + if (publicImportSet.count(import)) out << "@_exported "; // SPI attribute on imports if (Opts.PrintSPIs) { - SmallVector spis; - M->lookupImportedSPIGroups(importedModule, spis); for (auto spiName : spis) out << "@_spi(" << spiName << ") "; } diff --git a/test/SPI/experimental_spi_imports_swiftinterface.swift b/test/SPI/experimental_spi_imports_swiftinterface.swift new file mode 100644 index 0000000000000..76a50f21796da --- /dev/null +++ b/test/SPI/experimental_spi_imports_swiftinterface.swift @@ -0,0 +1,26 @@ +/// Test the textual interfaces generated with -experimental-spi-imports. + +// RUN: %empty-directory(%t) + +/// Generate 3 empty modules. +// RUN: touch %t/empty.swift +// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name ExperimentalImported -emit-module-path %t/ExperimentalImported.swiftmodule -swift-version 5 -enable-library-evolution +// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name IOIImported -emit-module-path %t/IOIImported.swiftmodule -swift-version 5 -enable-library-evolution +// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name SPIImported -emit-module-path %t/SPIImported.swiftmodule -swift-version 5 -enable-library-evolution + +/// Test the generated 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 -experimental-spi-imports +// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/main.swiftinterface +// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/main.private.swiftinterface + +@_spi(dummy) @_implementationOnly import ExperimentalImported +// CHECK-PUBLIC-NOT: import ExperimentalImported +// CHECK-PRIVATE: @_implementationOnly @_spi{{.*}} import ExperimentalImported + +@_implementationOnly import IOIImported +// CHECK-PUBLIC-NOT: IOIImported +// CHECK-PRIVATE-NOT: IOIImported + +@_spi(dummy) import SPIImported +// CHECK-PUBLIC: {{^}}import SPIImported +// CHECK-PRIVATE: @_spi{{.*}} import SPIImported diff --git a/test/SPI/experimental_spi_imports_type_check.swift b/test/SPI/experimental_spi_imports_type_check.swift new file mode 100644 index 0000000000000..be728e5c3aa27 --- /dev/null +++ b/test/SPI/experimental_spi_imports_type_check.swift @@ -0,0 +1,50 @@ +/// Test the use of implementation-only types with -experimental-spi-imports. + +/// Build LibCore an internal module and LibPublic a public module using LibCore. +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -DLIB_CORE %s -module-name LibCore -emit-module-path %t/LibCore.swiftmodule -enable-library-evolution -swift-version 5 +// RUN: %target-swift-frontend -emit-module -DLIB_PUBLIC %s -module-name LibPublic -emit-module-path %t/LibPublic.swiftmodule -I %t -emit-module-interface-path %t/LibPublic.swiftinterface -emit-private-module-interface-path %t/LibPublic.private.swiftinterface -enable-library-evolution -swift-version 5 -experimental-spi-imports + +/// Test with the swiftmodule file, the compiler raises an error only when +/// LibCore isn't loaded by the client. +// RUN: %target-typecheck-verify-swift -DCLIENT -I %t +// RUN: %target-swift-frontend -typecheck %s -DCLIENT -DCLIENT_LOAD_CORE -I %t + +/// Test with the private swiftinterface file, the compiler raises an error +/// only when LibCore isn't loaded by the client. +// RUN: rm %t/LibPublic.swiftmodule +// RUN: %target-typecheck-verify-swift -DCLIENT -I %t +// RUN: %target-swift-frontend -typecheck %s -DCLIENT -DCLIENT_LOAD_CORE -I %t + +/// Test with the public swiftinterface file, the SPI is unknown. +// RUN: rm %t/LibPublic.private.swiftinterface +// RUN: %target-typecheck-verify-swift -DCLIENT -I %t +// RUN: %target-typecheck-verify-swift -DCLIENT -DCLIENT_LOAD_CORE -I %t + +#if LIB_CORE + +public struct CoreStruct { + public init() {} + public func coreMethod() {} +} + + +#elseif LIB_PUBLIC + +@_spi(dummy) @_implementationOnly import LibCore + +@_spi(A) public func SPIFunc() -> CoreStruct { return CoreStruct() } + + +#elseif CLIENT + +@_spi(A) import LibPublic + +#if CLIENT_LOAD_CORE +import LibCore +#endif + +let x = SPIFunc() // expected-error {{cannot find 'SPIFunc' in scope}} +x.coreMethod() + +#endif