Skip to content

Commit 63d9acd

Browse files
committed
[Sema] Report public use of local SPIs by the exportability checker
This change improves slightly the quality of the diagnostics and detects the use of local SPI types on public property with a default value.
1 parent d2b68c7 commit 63d9acd

File tree

5 files changed

+62
-70
lines changed

5 files changed

+62
-70
lines changed

include/swift/AST/DiagnosticsSema.def

+4-2
Original file line numberDiff line numberDiff line change
@@ -2692,7 +2692,8 @@ ERROR(decl_from_hidden_module,none,
26922692
"in an extension with public or '@usableFromInline' members|"
26932693
"in an extension with conditional conformances}2; "
26942694
"%select{%3 has been imported as implementation-only|"
2695-
"it is an SPI imported from %3}4",
2695+
"it is an SPI imported from %3|"
2696+
"it is SPI}4",
26962697
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
26972698
ERROR(conformance_from_implementation_only_module,none,
26982699
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
@@ -4766,7 +4767,8 @@ WARNING(resilience_decl_unavailable_warn,
47664767
ERROR(inlinable_decl_ref_from_hidden_module,
47674768
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
47684769
"because %select{%3 was imported implementation-only|"
4769-
"it is an SPI imported from %3}4",
4770+
"it is an SPI imported from %3|"
4771+
"it is SPI}4",
47704772
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
47714773

47724774
#undef FRAGILE_FUNC_KIND

lib/AST/Decl.cpp

+1-9
Original file line numberDiff line numberDiff line change
@@ -3550,17 +3550,9 @@ static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
35503550
return useSF && useSF->hasTestableOrPrivateImport(access, sourceModule);
35513551
}
35523552
case AccessLevel::Public:
3553-
case AccessLevel::Open: {
3554-
if (useDC && VD->isSPI()) {
3555-
auto useModuleScopeContext = useDC->getModuleScopeContext();
3556-
if (useModuleScopeContext == sourceDC->getModuleScopeContext()) return true;
3557-
3558-
auto *useSF = dyn_cast<SourceFile>(useModuleScopeContext);
3559-
return !useSF || useSF->isImportedAsSPI(VD);
3560-
}
3553+
case AccessLevel::Open:
35613554
return true;
35623555
}
3563-
}
35643556
llvm_unreachable("bad access level");
35653557
}
35663558

lib/Sema/TypeCheckAccess.cpp

+46-51
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,7 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
235235
contextAccessScope.isChildOf(*typeReprAccessScope)) {
236236
// Only if both the Type and the TypeRepr follow the access rules can
237237
// we exit; otherwise we have to emit a diagnostic.
238-
239-
if (typeReprAccessScope->isPublic() != contextAccessScope.isPublic() ||
240-
!typeReprAccessScope->isSPI() ||
241-
contextAccessScope.isSPI()) {
242-
// And we exit only if there is no SPI violation.
243-
return;
244-
}
238+
return;
245239
}
246240
problematicAccessScope = *typeReprAccessScope;
247241

@@ -349,8 +343,7 @@ void AccessControlCheckerBase::checkGenericParamAccess(
349343
(thisDowngrade == DowngradeToWarning::No &&
350344
downgradeToWarning == DowngradeToWarning::Yes) ||
351345
(!complainRepr &&
352-
typeAccessScope.hasEqualDeclContextWith(minAccessScope)) ||
353-
typeAccessScope.isSPI()) {
346+
typeAccessScope.hasEqualDeclContextWith(minAccessScope))) {
354347
minAccessScope = typeAccessScope;
355348
complainRepr = thisComplainRepr;
356349
accessControlErrorKind = callbackACEK;
@@ -375,7 +368,7 @@ void AccessControlCheckerBase::checkGenericParamAccess(
375368
const_cast<GenericContext *>(ownerCtx)),
376369
accessScope, DC, callback);
377370

378-
if (minAccessScope.isPublic() && !minAccessScope.isSPI())
371+
if (minAccessScope.isPublic())
379372
return;
380373

381374
// FIXME: Promote these to an error in the next -swift-version break.
@@ -958,7 +951,7 @@ class AccessControlChecker : public AccessControlCheckerBase,
958951
});
959952
}
960953

961-
if (!minAccessScope.isPublic() || minAccessScope.isSPI()) {
954+
if (!minAccessScope.isPublic()) {
962955
auto minAccess = minAccessScope.accessLevelForDiagnostics();
963956
auto functionKind = isa<ConstructorDecl>(fn)
964957
? FK_Initializer
@@ -970,24 +963,13 @@ class AccessControlChecker : public AccessControlCheckerBase,
970963
? fn->getFormalAccess()
971964
: minAccessScope.requiredAccessForDiagnostics();
972965

973-
// Report as an SPI problem if the type is at least as public as the decl.
974-
AccessScope contextAccessScope =
975-
fn->getFormalAccessScope(fn->getDeclContext(), checkUsableFromInline);
976-
977-
if (contextAccessScope.isSPI()) {
978-
auto diag = fn->diagnose(diag::function_type_spi, functionKind,
979-
problemIsResult, minAccess,
980-
minAccess >= AccessLevel::Public);
981-
highlightOffendingType(diag, complainRepr);
982-
} else {
983-
auto diagID = diag::function_type_access;
984-
if (downgradeToWarning == DowngradeToWarning::Yes)
985-
diagID = diag::function_type_access_warn;
986-
auto diag = fn->diagnose(diagID, isExplicit, fnAccess,
987-
isa<FileUnit>(fn->getDeclContext()), minAccess,
988-
functionKind, problemIsResult);
989-
highlightOffendingType(diag, complainRepr);
990-
}
966+
auto diagID = diag::function_type_access;
967+
if (downgradeToWarning == DowngradeToWarning::Yes)
968+
diagID = diag::function_type_access_warn;
969+
auto diag = fn->diagnose(diagID, isExplicit, fnAccess,
970+
isa<FileUnit>(fn->getDeclContext()), minAccess,
971+
functionKind, problemIsResult);
972+
highlightOffendingType(diag, complainRepr);
991973
}
992974
}
993975

@@ -1471,6 +1453,31 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14711453
class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14721454
class Diagnoser;
14731455

1456+
// Problematic origin of an exported type.
1457+
//
1458+
// This enum must be kept in sync with
1459+
// diag::decl_from_hidden_module and
1460+
// diag::conformance_from_implementation_only_module.
1461+
enum class DisallowedOriginKind : uint8_t {
1462+
ImplementationOnly,
1463+
SPIImported,
1464+
SPILocal,
1465+
None
1466+
};
1467+
1468+
// If there's an exportability problem with \p typeDecl, get its origin kind.
1469+
static DisallowedOriginKind getDisallowedOriginKind(
1470+
const TypeDecl *typeDecl, const SourceFile &SF, const Decl *context) {
1471+
ModuleDecl *M = typeDecl->getModuleContext();
1472+
if (SF.isImportedImplementationOnly(M))
1473+
return DisallowedOriginKind::ImplementationOnly;
1474+
else if (typeDecl->isSPI() && !context->isSPI())
1475+
return context->getModuleContext() == M ?
1476+
DisallowedOriginKind::SPILocal :
1477+
DisallowedOriginKind::SPIImported;
1478+
return DisallowedOriginKind::None;
1479+
};
1480+
14741481
void checkTypeImpl(
14751482
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
14761483
const Decl *context,
@@ -1487,11 +1494,9 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14871494
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
14881495
[&](const ComponentIdentTypeRepr *component) {
14891496
TypeDecl *typeDecl = component->getBoundDecl();
1490-
ModuleDecl *M = typeDecl->getModuleContext();
1491-
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1492-
if (isImplementationOnly ||
1493-
(SF.isImportedAsSPI(typeDecl) && !context->isSPI())) {
1494-
diagnoser.diagnoseType(typeDecl, component, isImplementationOnly);
1497+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context);
1498+
if (originKind != DisallowedOriginKind::None) {
1499+
diagnoser.diagnoseType(typeDecl, component, originKind);
14951500
foundAnyIssues = true;
14961501
}
14971502

@@ -1519,12 +1524,9 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15191524
: SF(SF), context(context), diagnoser(diagnoser) {}
15201525

15211526
void visitTypeDecl(const TypeDecl *typeDecl) {
1522-
ModuleDecl *M = typeDecl->getModuleContext();
1523-
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1524-
if (isImplementationOnly ||
1525-
(SF.isImportedAsSPI(typeDecl) && !context->isSPI()))
1526-
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1527-
isImplementationOnly);
1527+
auto originKind = getDisallowedOriginKind(typeDecl, SF, context);
1528+
if (originKind != DisallowedOriginKind::None)
1529+
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr, originKind);
15281530
}
15291531

15301532
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1621,7 +1623,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16211623
});
16221624
}
16231625

1624-
// These enums must be kept in sync with
1626+
// This enum must be kept in sync with
16251627
// diag::decl_from_hidden_module and
16261628
// diag::conformance_from_implementation_only_module.
16271629
enum class Reason : unsigned {
@@ -1630,10 +1632,6 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16301632
ExtensionWithPublicMembers,
16311633
ExtensionWithConditionalConformances
16321634
};
1633-
enum class HiddenImportKind : uint8_t {
1634-
ImplementationOnly,
1635-
SPI
1636-
};
16371635

16381636
class Diagnoser {
16391637
const Decl *D;
@@ -1643,16 +1641,13 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16431641

16441642
void diagnoseType(const TypeDecl *offendingType,
16451643
const TypeRepr *complainRepr,
1646-
bool isImplementationOnly) const {
1644+
DisallowedOriginKind originKind) const {
16471645
ModuleDecl *M = offendingType->getModuleContext();
1648-
HiddenImportKind importKind = isImplementationOnly?
1649-
HiddenImportKind::ImplementationOnly:
1650-
HiddenImportKind::SPI;
16511646
auto diag = D->diagnose(diag::decl_from_hidden_module,
16521647
offendingType->getDescriptiveKind(),
16531648
offendingType->getName(),
16541649
static_cast<unsigned>(reason), M->getName(),
1655-
static_cast<unsigned>(importKind));
1650+
static_cast<unsigned>(originKind));
16561651
highlightOffendingType(diag, complainRepr);
16571652
}
16581653

@@ -1981,7 +1976,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
19811976
DE.diagnose(diagLoc, diag::decl_from_hidden_module,
19821977
PGD->getDescriptiveKind(), PGD->getName(),
19831978
static_cast<unsigned>(Reason::General), M->getName(),
1984-
static_cast<unsigned>(HiddenImportKind::ImplementationOnly)
1979+
static_cast<unsigned>(DisallowedOriginKind::ImplementationOnly)
19851980
);
19861981
if (refRange.isValid())
19871982
diag.highlight(refRange);

test/SPI/local_spi_decls.swift

+11-6
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
@_spi() public func emptyParensSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
1111
@_spi(set) public func keywordSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
1212

13-
@_spi(S) public class SPIClass {} // expected-note 2 {{type declared here}}
13+
@_spi(S) public class SPIClass {} // expected-note 3 {{type declared here}}
1414
// expected-note @-1 2 {{class 'SPIClass' is not '@usableFromInline' or public}}
1515
class InternalClass {} // expected-note 2 {{type declared here}}
1616
private class PrivateClass {} // expected-note 2 {{type declared here}}
1717

1818
@_spi(S) public protocol SPIProtocol {} // expected-note {{type declared here}}
1919

2020
@_spi(S) public func useOfSPITypeOk(_ p0: SPIProtocol, p1: SPIClass) -> SPIClass { fatalError() } // OK
21-
public func useOfSPITypeInvalid() -> SPIClass { fatalError() } // expected-error {{function cannot be declared public because its result uses an '@_spi' type}}
22-
@_spi(S) public func spiUseOfInternalType() -> InternalClass { fatalError() } // expected-error{{function cannot be declared '@_spi' because its result uses an internal type}}
23-
@_spi(S) public func spiUseOfPrivateType(_ a: PrivateClass) { fatalError() } // expected-error{{function cannot be declared '@_spi' because its parameter uses a private type}}
21+
public func useOfSPITypeInvalid() -> SPIClass { fatalError() } // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
22+
@_spi(S) public func spiUseOfInternalType() -> InternalClass { fatalError() } // expected-error{{function cannot be declared public because its result uses an internal type}}
23+
@_spi(S) public func spiUseOfPrivateType(_ a: PrivateClass) { fatalError() } // expected-error{{function cannot be declared public because its parameter uses a private type}}
2424

2525
@inlinable
2626
func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}
@@ -41,12 +41,17 @@ private protocol PrivateProtocol {} // expected-note {{type declared here}}
4141

4242
@_spi(S) public class BadSubclass : InternalClass {} // expected-error{{class cannot be declared public because its superclass is internal}}
4343
@_spi(S) public class OkSPISubclass : SPIClass {} // OK
44-
public class BadPublicClass : SPIClass {} // expected-error {{class cannot be declared public because its superclass is '@_spi'}}
44+
public class BadPublicClass : SPIClass {} // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
4545
@_spi(S) public class BadSPIClass : PrivateClass {} // expected-error {{class cannot be declared public because its superclass is private}}
4646

4747
@_spi(s) public func genFunc<T: PrivateProtocol>(_ t: T) {} // expected-error {{global function cannot be declared public because its generic parameter uses a private type}}
48-
public func genFuncBad<T: SPIProtocol>(_ t: T) {} // expected-error {{global function cannot be declared public because its generic parameter uses an '@_spi' type}}
48+
public func genFuncBad<T: SPIProtocol>(_ t: T) {} // expected-error {{cannot use protocol 'SPIProtocol' here; it is SPI}}
4949
@_spi(S) public func genFuncSPI<T: SPIProtocol>(_ t: T) {} // OK
5050

5151
@_spi(S) private func privateCantBeSPI() {} // expected-error{{private global function cannot be declared '@_spi' because only public and open declarations can be '@_spi'}} {{1-10=}}
5252
@_spi(S) func internalCantBeSPI() {} // expected-error{{internal global function cannot be declared '@_spi' because only public and open declarations can be '@_spi'}} {{1-10=}}
53+
54+
public struct PublicStructWithProperties {
55+
public var a: SPIClass // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
56+
public var b = SPIClass() // expected-error {{cannot use class 'SPIClass' here; it is SPI}}
57+
}

test/SPI/spi_client.swift

-2
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ print(ps.spiVar)
5050
otherApiFunc() // expected-error {{cannot find 'otherApiFunc' in scope}}
5151

5252
public func publicUseOfSPI(param: SPIClass) -> SPIClass {} // expected-error 2{{cannot use class 'SPIClass' here; it is an SPI imported from 'SPIHelper'}}
53-
// expected-error@-1 {{function cannot be declared public because its parameter uses an '@_spi' type}}
5453
public func publicUseOfSPI2() -> [SPIClass] {} // expected-error {{cannot use class 'SPIClass' here; it is an SPI imported from 'SPIHelper'}}
55-
// expected-error@-1 {{function cannot be declared public because its result uses an '@_spi' type}}
5654

5755
@inlinable
5856
func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' and cannot be referenced from an '@inlinable' function}}

0 commit comments

Comments
 (0)