Skip to content

Commit 5210dcb

Browse files
committed
[Sema] An SPI protocol requirement must have a default implementation
1 parent efc4e7d commit 5210dcb

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,10 @@ ERROR(spi_attribute_on_non_public,none,
17051705
"cannot be declared '@_spi' because only public and open "
17061706
"declarations can be '@_spi'",
17071707
(AccessLevel, DescriptiveDeclKind))
1708+
ERROR(spi_attribute_on_protocol_requirement,none,
1709+
"protocol requirement %0 cannot be declared '@_spi' without "
1710+
"a default implementation in a protocol extension",
1711+
(DeclName))
17081712

17091713
// Opaque return types
17101714
ERROR(opaque_type_invalid_constraint,none,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,13 +863,52 @@ void AttributeChecker::visitSetterAccessAttr(
863863

864864
void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) {
865865
if (auto VD = dyn_cast<ValueDecl>(D)) {
866+
// VD must be public or open to use an @_spi attribute.
866867
auto declAccess = VD->getFormalAccess();
867868
if (declAccess < AccessLevel::Public) {
868869
diagnoseAndRemoveAttr(attr,
869870
diag::spi_attribute_on_non_public,
870871
declAccess,
871872
D->getDescriptiveKind());
872873
}
874+
875+
// If VD is a public protocol requirement it can be SPI only if there's
876+
// a default implementation.
877+
if (auto protocol = dyn_cast<ProtocolDecl>(D->getDeclContext())) {
878+
auto implementations = TypeChecker::lookupMember(
879+
D->getDeclContext(),
880+
protocol->getDeclaredType(),
881+
VD->createNameRef(),
882+
NameLookupFlags::ProtocolMembers);
883+
bool hasDefaultImplementation = llvm::any_of(implementations,
884+
[&](const LookupResultEntry &entry) {
885+
auto entryDecl = entry.getValueDecl();
886+
auto DC = entryDecl->getDeclContext();
887+
auto extension = dyn_cast<ExtensionDecl>(DC);
888+
889+
// The implementation must be defined in the same module in
890+
// an unconstrained extension.
891+
if (!extension ||
892+
extension->getParentModule() != protocol->getParentModule() ||
893+
extension->isConstrainedExtension())
894+
return false;
895+
896+
// For computed properties and subscripts, check that the default
897+
// implementation defines `set` if the protocol declares it.
898+
if (auto protoStorage = dyn_cast<AbstractStorageDecl>(VD))
899+
if (auto entryStorage = dyn_cast<AbstractStorageDecl>(entryDecl))
900+
if (protoStorage->getAccessor(AccessorKind::Set) &&
901+
!entryStorage->getAccessor(AccessorKind::Set))
902+
return false;
903+
904+
return true;
905+
});
906+
907+
if (!hasDefaultImplementation)
908+
diagnoseAndRemoveAttr(attr,
909+
diag::spi_attribute_on_protocol_requirement,
910+
VD->getName());
911+
}
873912
}
874913
}
875914

test/SPI/protocol_requirement.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Test limitations on SPI protocol requirements.
2+
3+
// RUN: %target-typecheck-verify-swift
4+
5+
// Reject SPI protocol requirements without a default implementation.
6+
public protocol PublicProtoRejected {
7+
@_spi(Private) // expected-error{{protocol requirement 'reqWithoutDefault()' cannot be declared '@_spi' without a default implementation in a protocol extension}}
8+
func reqWithoutDefault()
9+
10+
@_spi(Private) // expected-error{{protocol requirement 'property' cannot be declared '@_spi' without a default implementation in a protocol extension}}
11+
var property: Int { get set }
12+
13+
@_spi(Private) // expected-error{{protocol requirement 'propertyWithoutSetter' cannot be declared '@_spi' without a default implementation in a protocol extension}}
14+
var propertyWithoutSetter: Int { get set }
15+
16+
@_spi(Private) // expected-error{{protocol requirement 'subscript(_:)' cannot be declared '@_spi' without a default implementation in a protocol extension}}
17+
subscript(index: Int) -> Int { get set }
18+
19+
@_spi(Private) // expected-error{{protocol requirement 'init()' cannot be declared '@_spi' without a default implementation in a protocol extension}}
20+
init()
21+
22+
@_spi(Private) // expected-error{{'@_spi' attribute cannot be applied to this declaration}}
23+
associatedtype T
24+
}
25+
26+
extension PublicProtoRejected {
27+
@_spi(Private)
28+
public var propertyWithoutSetter: Int { get { return 42 } }
29+
}
30+
31+
extension PublicProtoRejected where Self : Equatable {
32+
@_spi(Private)
33+
public func reqWithoutDefault() {
34+
// constrainted implementation
35+
}
36+
}
37+
38+
// Accept SPI protocol requirements with an implementation.
39+
public protocol PublicProto {
40+
@_spi(Private)
41+
func reqWithDefaultImplementation()
42+
43+
@_spi(Private)
44+
var property: Int { get set }
45+
46+
@_spi(Private)
47+
subscript(index: Int) -> Int { get set }
48+
49+
@_spi(Private)
50+
init()
51+
}
52+
53+
extension PublicProto {
54+
@_spi(Private)
55+
public func reqWithDefaultImplementation() { }
56+
57+
@_spi(Private)
58+
public var property: Int {
59+
get { return 42 }
60+
set { }
61+
}
62+
63+
@_spi(Private)
64+
public subscript(index: Int) -> Int {
65+
get { return 42 }
66+
set { }
67+
}
68+
69+
@_spi(Private)
70+
public init() { }
71+
}

0 commit comments

Comments
 (0)