Skip to content

Commit da8aff0

Browse files
authored
Merge pull request #33213 from xymus/spi-ioi-check-workaround-5.3
[5.3][Sema] Warn on exporting implementation-only imported types in SPI
2 parents a8dcb57 + d8b7742 commit da8aff0

File tree

3 files changed

+118
-32
lines changed

3 files changed

+118
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,6 +2684,12 @@ ERROR(decl_from_hidden_module,none,
26842684
"%select{%3 has been imported as implementation-only|"
26852685
"it is an SPI imported from %3}4",
26862686
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
2687+
WARNING(decl_from_hidden_module_warn,none,
2688+
"cannot use %0 %1 %select{in SPI|as property wrapper in SPI|"
2689+
"in an extension with public or '@usableFromInline' members|"
2690+
"in an extension with conditional conformances}2; "
2691+
"%select{%3 has been imported as implementation-only}4",
2692+
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
26872693
ERROR(conformance_from_implementation_only_module,none,
26882694
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
26892695
"in an extension with public or '@usableFromInline' members|"

lib/Sema/TypeCheckAccess.cpp

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,8 +1486,42 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14861486
class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14871487
class Diagnoser;
14881488

1489+
// Problematic origin of an exported type.
1490+
//
1491+
// This enum must be kept in sync with
1492+
// diag::decl_from_hidden_module and
1493+
// diag::conformance_from_implementation_only_module.
1494+
enum class DisallowedOriginKind : uint8_t {
1495+
ImplementationOnly,
1496+
SPIImported,
1497+
None
1498+
};
1499+
1500+
// If there's an exportability problem with \p typeDecl, get its origin kind.
1501+
static DisallowedOriginKind getDisallowedOriginKind(
1502+
const TypeDecl *typeDecl, const SourceFile &SF, const Decl *context,
1503+
DowngradeToWarning &downgradeToWarning) {
1504+
downgradeToWarning = DowngradeToWarning::No;
1505+
ModuleDecl *M = typeDecl->getModuleContext();
1506+
if (SF.isImportedImplementationOnly(M)) {
1507+
// Temporarily downgrade implementation-only exportability in SPI to
1508+
// a warning.
1509+
if (context->isSPI())
1510+
downgradeToWarning = DowngradeToWarning::Yes;
1511+
1512+
// Implementation-only imported, cannot be reexported.
1513+
return DisallowedOriginKind::ImplementationOnly;
1514+
} else if (SF.isImportedAsSPI(typeDecl) && !context->isSPI()) {
1515+
// SPI can only be exported in SPI.
1516+
return DisallowedOriginKind::SPIImported;
1517+
}
1518+
1519+
return DisallowedOriginKind::None;
1520+
};
1521+
14891522
void checkTypeImpl(
14901523
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
1524+
const Decl *context,
14911525
const Diagnoser &diagnoser) {
14921526
// Don't bother checking errors.
14931527
if (type && type->hasError())
@@ -1500,14 +1534,14 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15001534
if (typeRepr) {
15011535
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
15021536
[&](const ComponentIdentTypeRepr *component) {
1503-
ModuleDecl *M = component->getBoundDecl()->getModuleContext();
1504-
if (!SF.isImportedImplementationOnly(M) &&
1505-
!SF.isImportedAsSPI(component->getBoundDecl()))
1506-
return true;
1507-
1508-
diagnoser.diagnoseType(component->getBoundDecl(), component,
1509-
SF.isImportedImplementationOnly(M));
1510-
foundAnyIssues = true;
1537+
TypeDecl *typeDecl = component->getBoundDecl();
1538+
auto downgradeToWarning = DowngradeToWarning::No;
1539+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context, downgradeToWarning);
1540+
if (originKind != DisallowedOriginKind::None) {
1541+
diagnoser.diagnoseType(typeDecl, component, originKind, downgradeToWarning);
1542+
foundAnyIssues = true;
1543+
}
1544+
15111545
// We still continue even in the diagnostic case to report multiple
15121546
// violations.
15131547
return true;
@@ -1525,19 +1559,17 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15251559

15261560
class ProblematicTypeFinder : public TypeDeclFinder {
15271561
const SourceFile &SF;
1562+
const Decl *context;
15281563
const Diagnoser &diagnoser;
15291564
public:
1530-
ProblematicTypeFinder(const SourceFile &SF, const Diagnoser &diagnoser)
1531-
: SF(SF), diagnoser(diagnoser) {}
1565+
ProblematicTypeFinder(const SourceFile &SF, const Decl *context, const Diagnoser &diagnoser)
1566+
: SF(SF), context(context), diagnoser(diagnoser) {}
15321567

15331568
void visitTypeDecl(const TypeDecl *typeDecl) {
1534-
ModuleDecl *M = typeDecl->getModuleContext();
1535-
if (!SF.isImportedImplementationOnly(M) &&
1536-
!SF.isImportedAsSPI(typeDecl))
1537-
return;
1538-
1539-
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1540-
SF.isImportedImplementationOnly(M));
1569+
auto downgradeToWarning = DowngradeToWarning::No;
1570+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context, downgradeToWarning);
1571+
if (originKind != DisallowedOriginKind::None)
1572+
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr, originKind, downgradeToWarning);
15411573
}
15421574

15431575
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1597,15 +1629,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15971629
}
15981630
};
15991631

1600-
type.walk(ProblematicTypeFinder(SF, diagnoser));
1632+
type.walk(ProblematicTypeFinder(SF, context, diagnoser));
16011633
}
16021634

16031635
void checkType(
16041636
Type type, const TypeRepr *typeRepr, const Decl *context,
16051637
const Diagnoser &diagnoser) {
16061638
auto *SF = context->getDeclContext()->getParentSourceFile();
16071639
assert(SF && "checking a non-source declaration?");
1608-
return checkTypeImpl(type, typeRepr, *SF, diagnoser);
1640+
return checkTypeImpl(type, typeRepr, *SF, context, diagnoser);
16091641
}
16101642

16111643
void checkType(
@@ -1634,7 +1666,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16341666
});
16351667
}
16361668

1637-
// These enums must be kept in sync with
1669+
// This enum must be kept in sync with
16381670
// diag::decl_from_hidden_module and
16391671
// diag::conformance_from_implementation_only_module.
16401672
enum class Reason : unsigned {
@@ -1643,10 +1675,6 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16431675
ExtensionWithPublicMembers,
16441676
ExtensionWithConditionalConformances
16451677
};
1646-
enum class HiddenImportKind : uint8_t {
1647-
ImplementationOnly,
1648-
SPI
1649-
};
16501678

16511679
class Diagnoser {
16521680
const Decl *D;
@@ -1656,16 +1684,17 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16561684

16571685
void diagnoseType(const TypeDecl *offendingType,
16581686
const TypeRepr *complainRepr,
1659-
bool isImplementationOnly) const {
1687+
DisallowedOriginKind originKind,
1688+
DowngradeToWarning downgradeToWarning) const {
16601689
ModuleDecl *M = offendingType->getModuleContext();
1661-
HiddenImportKind importKind = isImplementationOnly?
1662-
HiddenImportKind::ImplementationOnly:
1663-
HiddenImportKind::SPI;
1664-
auto diag = D->diagnose(diag::decl_from_hidden_module,
1690+
auto errorOrWarning = downgradeToWarning == DowngradeToWarning::Yes?
1691+
diag::decl_from_hidden_module_warn:
1692+
diag::decl_from_hidden_module;
1693+
auto diag = D->diagnose(errorOrWarning,
16651694
offendingType->getDescriptiveKind(),
16661695
offendingType->getName(),
16671696
static_cast<unsigned>(reason), M->getName(),
1668-
static_cast<unsigned>(importKind));
1697+
static_cast<unsigned>(originKind));
16691698
highlightOffendingType(diag, complainRepr);
16701699
}
16711700

@@ -1702,7 +1731,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
17021731
AccessScope accessScope =
17031732
VD->getFormalAccessScope(nullptr,
17041733
/*treatUsableFromInlineAsPublic*/true);
1705-
if (accessScope.isPublic() && !accessScope.isSPI())
1734+
if (accessScope.isPublic())
17061735
return false;
17071736

17081737
// Is this a stored property in a non-resilient struct or class?
@@ -1994,7 +2023,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
19942023
DE.diagnose(diagLoc, diag::decl_from_hidden_module,
19952024
PGD->getDescriptiveKind(), PGD->getName(),
19962025
static_cast<unsigned>(Reason::General), M->getName(),
1997-
static_cast<unsigned>(HiddenImportKind::ImplementationOnly)
2026+
static_cast<unsigned>(DisallowedOriginKind::ImplementationOnly)
19982027
);
19992028
if (refRange.isValid())
20002029
diag.highlight(refRange);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// @_implementationOnly imported decls (SPI or not) should not be exposed in SPI.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend -emit-module -DLIB %s -module-name Lib -emit-module-path %t/Lib.swiftmodule
5+
// RUN: %target-typecheck-verify-swift -DCLIENT -I %t
6+
7+
#if LIB
8+
9+
@_spi(A) public func spiFunc() {}
10+
11+
@_spi(A) public struct SPIStruct {
12+
public init() {}
13+
}
14+
15+
@_spi(A) public protocol SPIProtocol {}
16+
17+
public func ioiFunc() {}
18+
19+
public struct IOIStruct {
20+
public init() {}
21+
}
22+
23+
public protocol IOIProtocol {}
24+
25+
#elseif CLIENT
26+
27+
@_spi(A) @_implementationOnly import Lib
28+
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}}
31+
32+
public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
33+
// expected-error @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
34+
public var spiStruct = SPIStruct() // expected-error {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
35+
public var ioiStruct = IOIStruct() // expected-error {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
36+
37+
@inlinable
38+
public func publicInlinable() {
39+
spiFunc() // expected-error {{global function 'spiFunc()' is '@_spi' and cannot be referenced from an '@inlinable' function}}
40+
ioiFunc() // expected-error {{global function 'ioiFunc()' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
41+
let s = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from an '@inlinable' function}}
42+
let i = IOIStruct() // expected-error {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
43+
}
44+
}
45+
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+
51+
#endif

0 commit comments

Comments
 (0)