Skip to content

Commit fb364ae

Browse files
authored
Merge pull request #33207 from xymus/spi-ioi-check-workaround
[ModuleInterface] Print some implementation-only imports in the private interface
2 parents 1e55916 + 73252e8 commit fb364ae

9 files changed

+156
-17
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,6 +2712,12 @@ ERROR(decl_from_hidden_module,none,
27122712
"it is an SPI imported from %3|"
27132713
"it is SPI}4",
27142714
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
2715+
WARNING(decl_from_hidden_module_warn,none,
2716+
"cannot use %0 %1 %select{in SPI|as property wrapper in SPI|"
2717+
"in an extension with public or '@usableFromInline' members|"
2718+
"in an extension with conditional conformances}2; "
2719+
"%select{%3 has been imported as implementation-only}4",
2720+
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
27152721
ERROR(conformance_from_implementation_only_module,none,
27162722
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
27172723
"in an extension with public or '@usableFromInline' members|"

include/swift/Frontend/ModuleInterfaceSupport.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ struct ModuleInterfaceOptions {
4343

4444
// Print SPI decls and attributes.
4545
bool PrintSPIs = false;
46+
47+
/// Print imports with both @_implementationOnly and @_spi, only applies
48+
/// when PrintSPIs is true.
49+
bool ExperimentalSPIImports = false;
4650
};
4751

4852
extern version::Version InterfaceFormatVersion;

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ def module_interface_preserve_types_as_written :
643643
HelpText<"When emitting a module interface, preserve types as they were "
644644
"written in the source">;
645645

646+
def experimental_spi_imports :
647+
Flag<["-"], "experimental-spi-imports">,
648+
HelpText<"Enable experimental support for SPI imports">;
649+
646650
def experimental_print_full_convention :
647651
Flag<["-"], "experimental-print-full-convention">,
648652
HelpText<"When emitting a module interface, emit additional @convention "

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ static void ParseModuleInterfaceArgs(ModuleInterfaceOptions &Opts,
313313
Args.hasArg(OPT_module_interface_preserve_types_as_written);
314314
Opts.PrintFullConvention |=
315315
Args.hasArg(OPT_experimental_print_full_convention);
316+
Opts.ExperimentalSPIImports |=
317+
Args.hasArg(OPT_experimental_spi_imports);
316318
}
317319

318320
/// Save a copy of any flags marked as ModuleInterfaceOption, if running

lib/Frontend/ModuleInterfaceSupport.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,28 @@ static void printImports(raw_ostream &out,
100100
ModuleDecl *M) {
101101
// FIXME: This is very similar to what's in Serializer::writeInputBlock, but
102102
// it's not obvious what higher-level optimization would be factored out here.
103+
ModuleDecl::ImportFilter allImportFilter;
104+
allImportFilter |= ModuleDecl::ImportFilterKind::Public;
105+
allImportFilter |= ModuleDecl::ImportFilterKind::Private;
106+
allImportFilter |= ModuleDecl::ImportFilterKind::SPIAccessControl;
107+
108+
// With -experimental-spi-imports:
109+
// When printing the private swiftinterface file, print implementation-only
110+
// imports only if they are also SPI. First, list all implementation-only
111+
// imports and filter them later.
112+
llvm::SmallSet<ModuleDecl::ImportedModule, 4,
113+
ModuleDecl::OrderImportedModules> ioiImportSet;
114+
if (Opts.PrintSPIs && Opts.ExperimentalSPIImports) {
115+
allImportFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
116+
117+
SmallVector<ModuleDecl::ImportedModule, 4> ioiImport;
118+
M->getImportedModules(ioiImport,
119+
ModuleDecl::ImportFilterKind::ImplementationOnly);
120+
ioiImportSet.insert(ioiImport.begin(), ioiImport.end());
121+
}
122+
103123
SmallVector<ModuleDecl::ImportedModule, 8> allImports;
104-
M->getImportedModules(allImports,
105-
{ModuleDecl::ImportFilterKind::Public,
106-
ModuleDecl::ImportFilterKind::Private,
107-
ModuleDecl::ImportFilterKind::SPIAccessControl});
124+
M->getImportedModules(allImports, allImportFilter);
108125
ModuleDecl::removeDuplicateImports(allImports);
109126
diagnoseScopedImports(M->getASTContext().Diags, allImports);
110127

@@ -124,13 +141,21 @@ static void printImports(raw_ostream &out,
124141
continue;
125142
}
126143

144+
llvm::SmallSetVector<Identifier, 4> spis;
145+
M->lookupImportedSPIGroups(importedModule, spis);
146+
147+
// Only print implementation-only imports which have an SPI import.
148+
if (ioiImportSet.count(import)) {
149+
if (spis.empty())
150+
continue;
151+
out << "@_implementationOnly ";
152+
}
153+
127154
if (publicImportSet.count(import))
128155
out << "@_exported ";
129156

130157
// SPI attribute on imports
131158
if (Opts.PrintSPIs) {
132-
llvm::SmallSetVector<Identifier, 4> spis;
133-
M->lookupImportedSPIGroups(importedModule, spis);
134159
for (auto spiName : spis)
135160
out << "@_spi(" << spiName << ") ";
136161
}

lib/Sema/TypeCheckAccess.cpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,14 +1467,25 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14671467

14681468
// If there's an exportability problem with \p typeDecl, get its origin kind.
14691469
static DisallowedOriginKind getDisallowedOriginKind(
1470-
const TypeDecl *typeDecl, const SourceFile &SF, const Decl *context) {
1470+
const TypeDecl *typeDecl, const SourceFile &SF, const Decl *context,
1471+
DowngradeToWarning &downgradeToWarning) {
1472+
downgradeToWarning = DowngradeToWarning::No;
14711473
ModuleDecl *M = typeDecl->getModuleContext();
1472-
if (SF.isImportedImplementationOnly(M))
1474+
if (SF.isImportedImplementationOnly(M)) {
1475+
// Temporarily downgrade implementation-only exportability in SPI to
1476+
// a warning.
1477+
if (context->isSPI())
1478+
downgradeToWarning = DowngradeToWarning::Yes;
1479+
1480+
// Implementation-only imported, cannot be reexported.
14731481
return DisallowedOriginKind::ImplementationOnly;
1474-
else if (typeDecl->isSPI() && !context->isSPI())
1482+
} else if (typeDecl->isSPI() && !context->isSPI()) {
1483+
// SPI can only be exported in SPI.
14751484
return context->getModuleContext() == M ?
14761485
DisallowedOriginKind::SPILocal :
14771486
DisallowedOriginKind::SPIImported;
1487+
}
1488+
14781489
return DisallowedOriginKind::None;
14791490
};
14801491

@@ -1494,9 +1505,10 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14941505
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
14951506
[&](const ComponentIdentTypeRepr *component) {
14961507
TypeDecl *typeDecl = component->getBoundDecl();
1497-
auto originKind = getDisallowedOriginKind(typeDecl, SF, context);
1508+
auto downgradeToWarning = DowngradeToWarning::No;
1509+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context, downgradeToWarning);
14981510
if (originKind != DisallowedOriginKind::None) {
1499-
diagnoser.diagnoseType(typeDecl, component, originKind);
1511+
diagnoser.diagnoseType(typeDecl, component, originKind, downgradeToWarning);
15001512
foundAnyIssues = true;
15011513
}
15021514

@@ -1524,9 +1536,10 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15241536
: SF(SF), context(context), diagnoser(diagnoser) {}
15251537

15261538
void visitTypeDecl(const TypeDecl *typeDecl) {
1527-
auto originKind = getDisallowedOriginKind(typeDecl, SF, context);
1539+
auto downgradeToWarning = DowngradeToWarning::No;
1540+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context, downgradeToWarning);
15281541
if (originKind != DisallowedOriginKind::None)
1529-
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr, originKind);
1542+
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr, originKind, downgradeToWarning);
15301543
}
15311544

15321545
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1641,9 +1654,13 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16411654

16421655
void diagnoseType(const TypeDecl *offendingType,
16431656
const TypeRepr *complainRepr,
1644-
DisallowedOriginKind originKind) const {
1657+
DisallowedOriginKind originKind,
1658+
DowngradeToWarning downgradeToWarning) const {
16451659
ModuleDecl *M = offendingType->getModuleContext();
1646-
auto diag = D->diagnose(diag::decl_from_hidden_module,
1660+
auto errorOrWarning = downgradeToWarning == DowngradeToWarning::Yes?
1661+
diag::decl_from_hidden_module_warn:
1662+
diag::decl_from_hidden_module;
1663+
auto diag = D->diagnose(errorOrWarning,
16471664
offendingType->getDescriptiveKind(),
16481665
offendingType->getName(),
16491666
static_cast<unsigned>(reason), M->getName(),
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Test the textual interfaces generated with -experimental-spi-imports.
2+
3+
// RUN: %empty-directory(%t)
4+
5+
/// Generate 3 empty modules.
6+
// RUN: touch %t/empty.swift
7+
// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name ExperimentalImported -emit-module-path %t/ExperimentalImported.swiftmodule -swift-version 5 -enable-library-evolution
8+
// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name IOIImported -emit-module-path %t/IOIImported.swiftmodule -swift-version 5 -enable-library-evolution
9+
// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name SPIImported -emit-module-path %t/SPIImported.swiftmodule -swift-version 5 -enable-library-evolution
10+
11+
/// Test the generated swiftinterface.
12+
// 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
13+
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/main.swiftinterface
14+
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/main.private.swiftinterface
15+
16+
@_spi(dummy) @_implementationOnly import ExperimentalImported
17+
// CHECK-PUBLIC-NOT: import ExperimentalImported
18+
// CHECK-PRIVATE: @_implementationOnly @_spi{{.*}} import ExperimentalImported
19+
20+
@_implementationOnly import IOIImported
21+
// CHECK-PUBLIC-NOT: IOIImported
22+
// CHECK-PRIVATE-NOT: IOIImported
23+
24+
@_spi(dummy) import SPIImported
25+
// CHECK-PUBLIC: {{^}}import SPIImported
26+
// CHECK-PRIVATE: @_spi{{.*}} import SPIImported
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/// Test the use of implementation-only types with -experimental-spi-imports.
2+
3+
/// Build LibCore an internal module and LibPublic a public module using LibCore.
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-swift-frontend -emit-module -DLIB_CORE %s -module-name LibCore -emit-module-path %t/LibCore.swiftmodule -enable-library-evolution -swift-version 5
6+
// 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
7+
8+
/// Test with the swiftmodule file, the compiler raises an error only when
9+
/// LibCore isn't loaded by the client.
10+
// RUN: %target-typecheck-verify-swift -DCLIENT -I %t
11+
// RUN: %target-swift-frontend -typecheck %s -DCLIENT -DCLIENT_LOAD_CORE -I %t
12+
13+
/// Test with the private swiftinterface file, the compiler raises an error
14+
/// only when LibCore isn't loaded by the client.
15+
// RUN: rm %t/LibPublic.swiftmodule
16+
// RUN: %target-typecheck-verify-swift -DCLIENT -I %t
17+
// RUN: %target-swift-frontend -typecheck %s -DCLIENT -DCLIENT_LOAD_CORE -I %t
18+
19+
/// Test with the public swiftinterface file, the SPI is unknown.
20+
// RUN: rm %t/LibPublic.private.swiftinterface
21+
// RUN: %target-typecheck-verify-swift -DCLIENT -I %t
22+
// RUN: %target-typecheck-verify-swift -DCLIENT -DCLIENT_LOAD_CORE -I %t
23+
24+
#if LIB_CORE
25+
26+
public struct CoreStruct {
27+
public init() {}
28+
public func coreMethod() {}
29+
}
30+
31+
32+
#elseif LIB_PUBLIC
33+
34+
@_spi(dummy) @_implementationOnly import LibCore
35+
36+
@_spi(A) public func SPIFunc() -> CoreStruct { return CoreStruct() }
37+
38+
39+
#elseif CLIENT
40+
41+
@_spi(A) import LibPublic
42+
43+
#if CLIENT_LOAD_CORE
44+
import LibCore
45+
#endif
46+
47+
let x = SPIFunc() // expected-error {{cannot find 'SPIFunc' in scope}}
48+
x.coreMethod()
49+
50+
#endif

test/SPI/implementation_only_spi_import_exposability.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public protocol IOIProtocol {}
2626

2727
@_spi(A) @_implementationOnly import Lib
2828

29-
@_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-error 2 {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
30-
@_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-error 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
29+
@_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'SPIStruct' in SPI; 'Lib' has been imported as implementation-only}}
30+
@_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'IOIStruct' in SPI; 'Lib' has been imported as implementation-only}}
3131

3232
public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
3333
// expected-error @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
@@ -43,4 +43,9 @@ public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cann
4343
}
4444
}
4545

46+
@_spi(B)
47+
public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' in SPI; 'Lib' has been imported as implementation-only}}
48+
// expected-warning @-1 {{cannot use protocol 'SPIProtocol' in SPI; 'Lib' has been imported as implementation-only}}
49+
}
50+
4651
#endif

0 commit comments

Comments
 (0)