Skip to content

Commit ca72167

Browse files
authored
Merge pull request #33686 from xymus/check-spi-conformances
[Sema] Check SPI conformances exportability
2 parents 9f2f1d8 + 3acbd09 commit ca72167

File tree

8 files changed

+721
-71
lines changed

8 files changed

+721
-71
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,13 +2739,17 @@ WARNING(decl_from_hidden_module_warn,none,
27392739
ERROR(conformance_from_implementation_only_module,none,
27402740
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
27412741
"in an extension with public or '@usableFromInline' members|"
2742-
"in an extension with conditional conformances}2; %3 has been imported "
2743-
"as implementation-only",
2744-
(Type, Identifier, unsigned, Identifier))
2742+
"in an extension with conditional conformances}2; "
2743+
"%select{%3 has been imported as implementation-only|"
2744+
"the conformance is declared as SPI in %3|"
2745+
"the conformance is declared as SPI}4",
2746+
(Type, Identifier, unsigned, Identifier, unsigned))
27452747
ERROR(assoc_conformance_from_implementation_only_module,none,
27462748
"cannot use conformance of %0 to %1 in associated type %3 (inferred as "
2747-
"%4); %2 has been imported as implementation-only",
2748-
(Type, Identifier, Identifier, Type, Type))
2749+
"%4); %select{%2 has been imported as implementation-only|"
2750+
"the conformance is declared as SPI in %2|"
2751+
"the conformance is declared as SPI}5",
2752+
(Type, Identifier, Identifier, Type, Type, unsigned))
27492753
ERROR(unexportable_clang_function_type,none,
27502754
"cannot export the underlying C type of the function type %0; "
27512755
"it may use anonymous types or types defined outside of a module",

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "TypeChecker.h"
1818
#include "TypeCheckAvailability.h"
19+
#include "TypeCheckAccess.h"
1920
#include "swift/AST/Attr.h"
2021
#include "swift/AST/Decl.h"
2122
#include "swift/AST/DeclContext.h"
@@ -171,7 +172,8 @@ static bool diagnoseDeclExportability(SourceLoc loc, const ValueDecl *D,
171172
static bool
172173
diagnoseGenericArgumentsExportability(SourceLoc loc,
173174
SubstitutionMap subs,
174-
const SourceFile &userSF) {
175+
const SourceFile &userSF,
176+
const DeclContext *userDC) {
175177
bool hadAnyIssues = false;
176178
for (ProtocolConformanceRef conformance : subs.getConformances()) {
177179
if (!conformance.isConcrete())
@@ -180,18 +182,23 @@ diagnoseGenericArgumentsExportability(SourceLoc loc,
180182

181183
SubstitutionMap subConformanceSubs =
182184
concreteConf->getSubstitutions(userSF.getParentModule());
183-
diagnoseGenericArgumentsExportability(loc, subConformanceSubs, userSF);
185+
diagnoseGenericArgumentsExportability(loc, subConformanceSubs, userSF, userDC);
184186

185187
const RootProtocolConformance *rootConf =
186188
concreteConf->getRootConformance();
187189
ModuleDecl *M = rootConf->getDeclContext()->getParentModule();
188-
if (!userSF.isImportedImplementationOnly(M))
190+
191+
auto originKind = getDisallowedOriginKind(
192+
rootConf->getDeclContext()->getAsDecl(),
193+
userSF, userDC->getInnermostDeclarationDeclContext());
194+
if (originKind == DisallowedOriginKind::None)
189195
continue;
190196

191197
ASTContext &ctx = M->getASTContext();
192198
ctx.Diags.diagnose(loc, diag::conformance_from_implementation_only_module,
193199
rootConf->getType(),
194-
rootConf->getProtocol()->getName(), 0, M->getName());
200+
rootConf->getProtocol()->getName(), 0, M->getName(),
201+
static_cast<unsigned>(originKind));
195202
hadAnyIssues = true;
196203
}
197204
return hadAnyIssues;
@@ -209,10 +216,10 @@ void TypeChecker::diagnoseGenericTypeExportability(SourceLoc Loc, Type T,
209216
if (auto *BGT = dyn_cast<BoundGenericType>(T.getPointer())) {
210217
ModuleDecl *useModule = SF->getParentModule();
211218
auto subs = T->getContextSubstitutionMap(useModule, BGT->getDecl());
212-
(void)diagnoseGenericArgumentsExportability(Loc, subs, *SF);
219+
(void)diagnoseGenericArgumentsExportability(Loc, subs, *SF, DC);
213220
} else if (auto *TAT = dyn_cast<TypeAliasType>(T.getPointer())) {
214221
auto subs = TAT->getSubstitutionMap();
215-
(void)diagnoseGenericArgumentsExportability(Loc, subs, *SF);
222+
(void)diagnoseGenericArgumentsExportability(Loc, subs, *SF, DC);
216223
}
217224
}
218225

@@ -226,19 +233,11 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
226233
if (!userSF)
227234
return false;
228235

229-
// If the source file doesn't have any implementation-only imports,
230-
// we can fast-path this. In the current language design, we never
231-
// need to consider the possibility of implementation-only imports
232-
// from other source files in the module (or indirectly in other modules).
233-
// TODO: maybe check whether D is from a bridging header?
234-
if (!userSF->hasImplementationOnlyImports())
235-
return false;
236-
237236
const ValueDecl *D = declRef.getDecl();
238237
if (diagnoseDeclExportability(loc, D, *userSF, fragileKind))
239238
return true;
240239
if (diagnoseGenericArgumentsExportability(loc, declRef.getSubstitutions(),
241-
*userSF)) {
240+
*userSF, DC)) {
242241
return true;
243242
}
244243
return false;

lib/Sema/TypeCheckAccess.cpp

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,47 +1454,40 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14541454
}
14551455
};
14561456

1457+
/// Returns the kind of origin, implementation-only import or SPI declaration,
1458+
/// that restricts exporting \p decl from the given file and context.
1459+
///
1460+
/// Local variant to swift::getDisallowedOriginKind for downgrade to warnings.
1461+
DisallowedOriginKind
1462+
getDisallowedOriginKind(const Decl *decl,
1463+
const SourceFile &userSF,
1464+
const Decl *userContext,
1465+
DowngradeToWarning &downgradeToWarning) {
1466+
downgradeToWarning = DowngradeToWarning::No;
1467+
ModuleDecl *M = decl->getModuleContext();
1468+
if (userSF.isImportedImplementationOnly(M)) {
1469+
// Temporarily downgrade implementation-only exportability in SPI to
1470+
// a warning.
1471+
if (userContext->isSPI())
1472+
downgradeToWarning = DowngradeToWarning::Yes;
1473+
1474+
// Implementation-only imported, cannot be reexported.
1475+
return DisallowedOriginKind::ImplementationOnly;
1476+
} else if (decl->isSPI() && !userContext->isSPI()) {
1477+
// SPI can only be exported in SPI.
1478+
return userContext->getModuleContext() == M ?
1479+
DisallowedOriginKind::SPILocal :
1480+
DisallowedOriginKind::SPIImported;
1481+
}
1482+
1483+
return DisallowedOriginKind::None;
1484+
};
1485+
14571486
// Diagnose public APIs exposing types that are either imported as
14581487
// implementation-only or declared as SPI.
14591488
class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14601489
class Diagnoser;
14611490

1462-
// Problematic origin of an exported type.
1463-
//
1464-
// This enum must be kept in sync with
1465-
// diag::decl_from_hidden_module and
1466-
// diag::conformance_from_implementation_only_module.
1467-
enum class DisallowedOriginKind : uint8_t {
1468-
ImplementationOnly,
1469-
SPIImported,
1470-
SPILocal,
1471-
None
1472-
};
1473-
1474-
// If there's an exportability problem with \p typeDecl, get its origin kind.
1475-
static DisallowedOriginKind getDisallowedOriginKind(
1476-
const TypeDecl *typeDecl, const SourceFile &SF, const Decl *context,
1477-
DowngradeToWarning &downgradeToWarning) {
1478-
downgradeToWarning = DowngradeToWarning::No;
1479-
ModuleDecl *M = typeDecl->getModuleContext();
1480-
if (SF.isImportedImplementationOnly(M)) {
1481-
// Temporarily downgrade implementation-only exportability in SPI to
1482-
// a warning.
1483-
if (context->isSPI())
1484-
downgradeToWarning = DowngradeToWarning::Yes;
1485-
1486-
// Implementation-only imported, cannot be reexported.
1487-
return DisallowedOriginKind::ImplementationOnly;
1488-
} else if (typeDecl->isSPI() && !context->isSPI()) {
1489-
// SPI can only be exported in SPI.
1490-
return context->getModuleContext() == M ?
1491-
DisallowedOriginKind::SPILocal :
1492-
DisallowedOriginKind::SPIImported;
1493-
}
1494-
1495-
return DisallowedOriginKind::None;
1496-
};
1497-
14981491
void checkTypeImpl(
14991492
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
15001493
const Decl *context,
@@ -1560,10 +1553,12 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15601553

15611554
const RootProtocolConformance *rootConf =
15621555
concreteConf->getRootConformance();
1563-
ModuleDecl *M = rootConf->getDeclContext()->getParentModule();
1564-
if (!SF.isImportedImplementationOnly(M))
1556+
auto originKind = getDisallowedOriginKind(
1557+
rootConf->getDeclContext()->getAsDecl(),
1558+
SF, context);
1559+
if (originKind == DisallowedOriginKind::None)
15651560
continue;
1566-
diagnoser.diagnoseConformance(rootConf);
1561+
diagnoser.diagnoseConformance(rootConf, originKind);
15671562
}
15681563
}
15691564

@@ -1674,12 +1669,14 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16741669
highlightOffendingType(diag, complainRepr);
16751670
}
16761671

1677-
void diagnoseConformance(const ProtocolConformance *offendingConformance) const {
1672+
void diagnoseConformance(const ProtocolConformance *offendingConformance,
1673+
DisallowedOriginKind originKind) const {
16781674
ModuleDecl *M = offendingConformance->getDeclContext()->getParentModule();
16791675
D->diagnose(diag::conformance_from_implementation_only_module,
16801676
offendingConformance->getType(),
16811677
offendingConformance->getProtocol()->getName(),
1682-
static_cast<unsigned>(reason), M->getName());
1678+
static_cast<unsigned>(reason), M->getName(),
1679+
static_cast<unsigned>(originKind));
16831680
}
16841681

16851682
void diagnoseClangFunctionType(Type fnType, const clang::Type *type) const {
@@ -2067,6 +2064,13 @@ static void checkExtensionGenericParamAccess(const ExtensionDecl *ED) {
20672064
ED, ED, desiredAccessScope, userSpecifiedAccess);
20682065
}
20692066

2067+
DisallowedOriginKind swift::getDisallowedOriginKind(const Decl *decl,
2068+
const SourceFile &userSF,
2069+
const Decl *declContext) {
2070+
auto downgradeToWarning = DowngradeToWarning::No;
2071+
return getDisallowedOriginKind(decl, userSF, declContext, downgradeToWarning);
2072+
}
2073+
20702074
void swift::checkAccessControl(Decl *D) {
20712075
if (isa<ValueDecl>(D) || isa<PatternBindingDecl>(D)) {
20722076
AccessControlChecker().visit(D);

lib/Sema/TypeCheckAccess.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
#ifndef TYPECHECKACCESS_H
1818
#define TYPECHECKACCESS_H
1919

20+
#include <cstdint>
21+
2022
namespace swift {
2123

2224
class Decl;
25+
class SourceFile;
2326

2427
/// Performs access-related checks for \p D.
2528
///
@@ -28,6 +31,24 @@ class Decl;
2831
/// itself. Related checks may also be performed.
2932
void checkAccessControl(Decl *D);
3033

34+
// Problematic origin of an exported type.
35+
//
36+
// This enum must be kept in sync with
37+
// diag::decl_from_hidden_module and
38+
// diag::conformance_from_implementation_only_module.
39+
enum class DisallowedOriginKind : uint8_t {
40+
ImplementationOnly,
41+
SPIImported,
42+
SPILocal,
43+
None
44+
};
45+
46+
/// Returns the kind of origin, implementation-only import or SPI declaration,
47+
/// that restricts exporting \p decl from the given file and context.
48+
DisallowedOriginKind getDisallowedOriginKind(const Decl *decl,
49+
const SourceFile &userSF,
50+
const Decl *userContext);
51+
3152
} // end namespace swift
3253

3354
#endif

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "DerivedConformances.h"
2020
#include "MiscDiagnostics.h"
2121
#include "TypeAccessScopeChecker.h"
22+
#include "TypeCheckAccess.h"
2223
#include "TypeCheckAvailability.h"
2324
#include "TypeCheckObjC.h"
2425
#include "swift/AST/ASTContext.h"
@@ -4039,7 +4040,8 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
40394040
static void checkExportability(Type depTy, Type replacementTy,
40404041
const ProtocolConformance *conformance,
40414042
NormalProtocolConformance *conformanceBeingChecked,
4042-
SourceFile *SF) {
4043+
DeclContext *DC) {
4044+
SourceFile *SF = DC->getParentSourceFile();
40434045
if (!SF)
40444046
return;
40454047

@@ -4049,13 +4051,17 @@ static void checkExportability(Type depTy, Type replacementTy,
40494051
if (!subConformance.isConcrete())
40504052
continue;
40514053
checkExportability(depTy, replacementTy, subConformance.getConcrete(),
4052-
conformanceBeingChecked, SF);
4054+
conformanceBeingChecked, DC);
40534055
}
40544056

40554057
const RootProtocolConformance *rootConformance =
40564058
conformance->getRootConformance();
40574059
ModuleDecl *M = rootConformance->getDeclContext()->getParentModule();
4058-
if (!SF->isImportedImplementationOnly(M))
4060+
4061+
auto originKind = getDisallowedOriginKind(
4062+
rootConformance->getDeclContext()->getAsDecl(),
4063+
*SF, DC->getAsDecl());
4064+
if (originKind == DisallowedOriginKind::None)
40594065
return;
40604066

40614067
ASTContext &ctx = SF->getASTContext();
@@ -4066,14 +4072,16 @@ static void checkExportability(Type depTy, Type replacementTy,
40664072
conformanceBeingChecked->getLoc(),
40674073
diag::conformance_from_implementation_only_module,
40684074
rootConformance->getType(),
4069-
rootConformance->getProtocol()->getName(), 0, M->getName());
4075+
rootConformance->getProtocol()->getName(), 0, M->getName(),
4076+
static_cast<unsigned>(originKind));
40704077
} else {
40714078
ctx.Diags.diagnose(
40724079
conformanceBeingChecked->getLoc(),
40734080
diag::assoc_conformance_from_implementation_only_module,
40744081
rootConformance->getType(),
40754082
rootConformance->getProtocol()->getName(), M->getName(),
4076-
depTy, replacementTy->getCanonicalType());
4083+
depTy, replacementTy->getCanonicalType(),
4084+
static_cast<unsigned>(originKind));
40774085
}
40784086
}
40794087

@@ -4122,11 +4130,7 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
41224130

41234131
// Now check that our associated conformances are at least as visible as
41244132
// the conformance itself.
4125-
//
4126-
// FIXME: Do we need to check SPI here too?
41274133
if (getRequiredAccessScope().isPublic() || isUsableFromInlineRequired()) {
4128-
auto *fileForCheckingExportability = DC->getParentSourceFile();
4129-
41304134
for (auto req : proto->getRequirementSignature()) {
41314135
if (req.getKind() == RequirementKind::Conformance) {
41324136
auto depTy = req.getFirstType();
@@ -4136,7 +4140,7 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
41364140
auto *concrete = conformance.getConcrete();
41374141
auto replacementTy = DC->mapTypeIntoContext(concrete->getType());
41384142
checkExportability(depTy, replacementTy, concrete,
4139-
Conformance, fileForCheckingExportability);
4143+
Conformance, DC);
41404144
}
41414145
}
41424146
}

test/SPI/protocol_requirement.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,20 @@ public protocol SPIProtocol {
9696
@_spi(Private)
9797
static var staticProperty: Int { get }
9898
}
99+
100+
public protocol OtherProto {}
101+
public protocol Proto {
102+
associatedtype A : Sequence where A.Element : OtherProto
103+
}
104+
105+
public struct BadStruct {}
106+
@_spi(Horse) extension BadStruct : OtherProto {}
107+
public struct BadConforms : Proto { // expected-error {{cannot use conformance of 'BadStruct' to 'OtherProto' in associated type 'Self.A.Element' (inferred as 'BadStruct'); the conformance is declared as SPI}}
108+
public typealias A = [BadStruct]
109+
}
110+
111+
public struct OKStruct {}
112+
@_spi(Horse) extension OKStruct : OtherProto {}
113+
@_spi(Horse) public struct OKConforms : Proto {
114+
public typealias A = [OKStruct]
115+
}

0 commit comments

Comments
 (0)