Skip to content

Commit 71fb262

Browse files
committed
Sema: implement requiresClass using a request evaluator.
Add the request `ProtocolRequiresClassRequest` to lazily determine if a `ProtocolDecl` requires conforming types to be a class. Note that using the request evaluator to compute `requiresClass` introduces cycle errors for protocol declarations, where this computation didn't previously emit diagnostics. For now, we'll allow duplicate diagnostics in this case, with the eventual goal of removing explicitly checking for cycles via `checkCircularity` (instead letting the request evaluator handle cycle diagnostics).
1 parent 8880aae commit 71fb262

File tree

10 files changed

+115
-57
lines changed

10 files changed

+115
-57
lines changed

include/swift/AST/Decl.h

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4105,7 +4105,20 @@ class ProtocolDecl final : public NominalTypeDecl {
41054105
/// by this protocol.
41064106
const Requirement *RequirementSignature = nullptr;
41074107

4108-
bool requiresClassSlow();
4108+
/// Returns the cached result of \c requiresClass or \c None if it hasn't yet
4109+
/// been computed.
4110+
Optional<bool> getCachedRequiresClass() const {
4111+
if (Bits.ProtocolDecl.RequiresClassValid)
4112+
return Bits.ProtocolDecl.RequiresClass;
4113+
4114+
return None;
4115+
}
4116+
4117+
/// Caches the result of \c requiresClass
4118+
void setCachedRequiresClass(bool requiresClass) {
4119+
Bits.ProtocolDecl.RequiresClassValid = true;
4120+
Bits.ProtocolDecl.RequiresClass = requiresClass;
4121+
}
41094122

41104123
bool existentialConformsToSelfSlow();
41114124

@@ -4120,6 +4133,7 @@ class ProtocolDecl final : public NominalTypeDecl {
41204133
friend class SuperclassDeclRequest;
41214134
friend class SuperclassTypeRequest;
41224135
friend class RequirementSignatureRequest;
4136+
friend class ProtocolRequiresClassRequest;
41234137
friend class TypeChecker;
41244138

41254139
public:
@@ -4183,19 +4197,7 @@ class ProtocolDecl final : public NominalTypeDecl {
41834197
}
41844198

41854199
/// True if this protocol can only be conformed to by class types.
4186-
bool requiresClass() const {
4187-
if (Bits.ProtocolDecl.RequiresClassValid)
4188-
return Bits.ProtocolDecl.RequiresClass;
4189-
4190-
return const_cast<ProtocolDecl *>(this)->requiresClassSlow();
4191-
}
4192-
4193-
/// Specify that this protocol is class-bounded, e.g., because it was
4194-
/// annotated with the 'class' keyword.
4195-
void setRequiresClass(bool requiresClass = true) {
4196-
Bits.ProtocolDecl.RequiresClassValid = true;
4197-
Bits.ProtocolDecl.RequiresClass = requiresClass;
4198-
}
4200+
bool requiresClass() const;
41994201

42004202
/// Determine whether an existential conforming to this protocol can be
42014203
/// matched with a generic type parameter constrained to this protocol.

include/swift/AST/DiagnosticsCommon.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ ERROR(circular_class_inheritance,none,
153153
ERROR(circular_enum_inheritance,none,
154154
"%0 has a raw type that depends on itself", (Identifier))
155155

156+
ERROR(circular_protocol_def,none,
157+
"protocol %0 refines itself", (Identifier))
158+
159+
NOTE(kind_declname_declared_here,none,
160+
"%0 %1 declared here", (DescriptiveDeclKind, DeclName))
161+
156162
#ifndef DIAG_NO_UNDEF
157163
# if defined(DIAG)
158164
# undef DIAG

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
DIAG(NOTE,ID,Options,Text,Signature)
3838
#endif
3939

40-
NOTE(kind_declname_declared_here,none,
41-
"%0 %1 declared here", (DescriptiveDeclKind, DeclName))
4240
NOTE(decl_declared_here,none,
4341
"%0 declared here", (DeclName))
4442
NOTE(kind_declared_here,none,
@@ -2037,8 +2035,6 @@ ERROR(typealias_outside_of_protocol,none,
20372035
"type alias %0 can only be used with a concrete type or "
20382036
"generic parameter base", (Identifier))
20392037

2040-
ERROR(circular_protocol_def,none,
2041-
"protocol %0 refines itself", (Identifier))
20422038
ERROR(objc_protocol_inherits_non_objc_protocol,none,
20432039
"@objc protocol %0 cannot refine non-@objc protocol %1", (Type, Type))
20442040

include/swift/AST/TypeCheckRequests.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,31 @@ class IsObjCRequest :
176176
void cacheResult(bool value) const;
177177
};
178178

179+
/// Determine whether the given protocol declaration is class-bounded.
180+
class ProtocolRequiresClassRequest:
181+
public SimpleRequest<ProtocolRequiresClassRequest,
182+
bool(ProtocolDecl *),
183+
CacheKind::SeparatelyCached> {
184+
public:
185+
using SimpleRequest::SimpleRequest;
186+
187+
private:
188+
friend SimpleRequest;
189+
190+
// Evaluation.
191+
llvm::Expected<bool> evaluate(Evaluator &evaluator, ProtocolDecl *decl) const;
192+
193+
public:
194+
// Cycle handling.
195+
void diagnoseCycle(DiagnosticEngine &diags) const;
196+
void noteCycleStep(DiagnosticEngine &diags) const;
197+
198+
// Separate caching.
199+
bool isCached() const { return true; }
200+
Optional<bool> getCachedResult() const;
201+
void cacheResult(bool value) const;
202+
};
203+
179204
/// Determine whether the given declaration is 'final'.
180205
class IsFinalRequest :
181206
public SimpleRequest<IsFinalRequest,

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ SWIFT_TYPEID(SuperclassTypeRequest)
1919
SWIFT_TYPEID(EnumRawTypeRequest)
2020
SWIFT_TYPEID(OverriddenDeclsRequest)
2121
SWIFT_TYPEID(IsObjCRequest)
22+
SWIFT_TYPEID(ProtocolRequiresClassRequest)
2223
SWIFT_TYPEID(IsFinalRequest)
2324
SWIFT_TYPEID(IsDynamicRequest)
2425
SWIFT_TYPEID(RequirementRequest)

lib/AST/Decl.cpp

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4320,41 +4320,9 @@ bool ProtocolDecl::inheritsFrom(const ProtocolDecl *super) const {
43204320
});
43214321
}
43224322

4323-
bool ProtocolDecl::requiresClassSlow() {
4324-
// Set this first to catch (invalid) circular inheritance.
4325-
Bits.ProtocolDecl.RequiresClassValid = true;
4326-
Bits.ProtocolDecl.RequiresClass = false;
4327-
4328-
// Quick check: @objc protocols require a class.
4329-
if (isObjC())
4330-
return Bits.ProtocolDecl.RequiresClass = true;
4331-
4332-
// Determine the set of nominal types that this protocol inherits.
4333-
bool anyObject = false;
4334-
auto allInheritedNominals =
4335-
getDirectlyInheritedNominalTypeDecls(this, anyObject);
4336-
4337-
// Quick check: do we inherit AnyObject?
4338-
if (anyObject) {
4339-
Bits.ProtocolDecl.RequiresClass = true;
4340-
return true;
4341-
}
4342-
4343-
// Look through all of the inherited nominals for a superclass or a
4344-
// class-bound protocol.
4345-
for (const auto found : allInheritedNominals) {
4346-
// Superclass bound.
4347-
if (isa<ClassDecl>(found.second))
4348-
return Bits.ProtocolDecl.RequiresClass = true;
4349-
4350-
// A protocol that might be class-constrained;
4351-
if (auto proto = dyn_cast<ProtocolDecl>(found.second)) {
4352-
if (proto->requiresClass())
4353-
return Bits.ProtocolDecl.RequiresClass = true;
4354-
}
4355-
}
4356-
4357-
return Bits.ProtocolDecl.RequiresClass;
4323+
bool ProtocolDecl::requiresClass() const {
4324+
return evaluateOrDefault(getASTContext().evaluator,
4325+
ProtocolRequiresClassRequest{const_cast<ProtocolDecl *>(this)}, false);
43584326
}
43594327

43604328
bool ProtocolDecl::requiresSelfConformanceWitnessTable() const {

lib/AST/TypeCheckRequests.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ void IsObjCRequest::cacheResult(bool value) const {
185185
decl->setIsObjC(value);
186186
}
187187

188+
//----------------------------------------------------------------------------//
189+
// requiresClass computation.
190+
//----------------------------------------------------------------------------//
191+
192+
void ProtocolRequiresClassRequest::diagnoseCycle(DiagnosticEngine &diags) const {
193+
auto decl = std::get<0>(getStorage());
194+
diags.diagnose(decl, diag::circular_protocol_def, decl->getName());
195+
}
196+
197+
void ProtocolRequiresClassRequest::noteCycleStep(DiagnosticEngine &diags) const {
198+
auto requirement = std::get<0>(getStorage());
199+
diags.diagnose(requirement, diag::kind_declname_declared_here,
200+
DescriptiveDeclKind::Protocol,
201+
requirement->getName());
202+
}
203+
204+
Optional<bool> ProtocolRequiresClassRequest::getCachedResult() const {
205+
auto decl = std::get<0>(getStorage());
206+
return decl->getCachedRequiresClass();
207+
}
208+
209+
void ProtocolRequiresClassRequest::cacheResult(bool value) const {
210+
auto decl = std::get<0>(getStorage());
211+
decl->setCachedRequiresClass(value);
212+
}
213+
188214
//----------------------------------------------------------------------------//
189215
// isFinal computation.
190216
//----------------------------------------------------------------------------//

lib/Sema/TypeCheckDecl.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,39 @@ static bool doesAccessorNeedDynamicAttribute(AccessorDecl *accessor) {
12361236
llvm_unreachable("covered switch");
12371237
}
12381238

1239+
llvm::Expected<bool>
1240+
ProtocolRequiresClassRequest::evaluate(Evaluator &evaluator,
1241+
ProtocolDecl *decl) const {
1242+
// Quick check: @objc protocols require a class.
1243+
if (decl->isObjC())
1244+
return true;
1245+
1246+
// Determine the set of nominal types that this protocol inherits.
1247+
bool anyObject = false;
1248+
auto allInheritedNominals =
1249+
getDirectlyInheritedNominalTypeDecls(decl, anyObject);
1250+
1251+
// Quick check: do we inherit AnyObject?
1252+
if (anyObject)
1253+
return true;
1254+
1255+
// Look through all of the inherited nominals for a superclass or a
1256+
// class-bound protocol.
1257+
for (const auto found : allInheritedNominals) {
1258+
// Superclass bound.
1259+
if (isa<ClassDecl>(found.second))
1260+
return true;
1261+
1262+
// A protocol that might be class-constrained.
1263+
if (auto proto = dyn_cast<ProtocolDecl>(found.second)) {
1264+
if (proto->requiresClass())
1265+
return true;
1266+
}
1267+
}
1268+
1269+
return false;
1270+
}
1271+
12391272
llvm::Expected<bool>
12401273
IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
12411274
if (isa<ClassDecl>(decl))

lib/Serialization/Deserialization.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3323,7 +3323,8 @@ class swift::DeclDeserializer {
33233323
None, /*TrailingWhere=*/nullptr);
33243324
declOrOffset = proto;
33253325

3326-
proto->setRequiresClass(isClassBounded);
3326+
ctx.evaluator.cacheOutput(ProtocolRequiresClassRequest{proto},
3327+
std::move(isClassBounded));
33273328
proto->setExistentialTypeSupported(existentialTypeSupported);
33283329

33293330
if (auto accessLevel = getActualAccessLevel(rawAccessLevel)) {

test/decl/protocol/protocols.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ struct DoesNotConform : Up {
101101

102102
// Circular protocols
103103

104-
protocol CircleMiddle : CircleStart { func circle_middle() } // expected-error {{protocol 'CircleMiddle' refines itself}}
104+
protocol CircleMiddle : CircleStart { func circle_middle() } // expected-error 2 {{protocol 'CircleMiddle' refines itself}}
105105
protocol CircleStart : CircleEnd { func circle_start() }
106-
// expected-note@-1{{protocol 'CircleStart' declared here}}
107-
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note{{protocol 'CircleEnd' declared here}}
106+
// expected-note@-1 2{{protocol 'CircleStart' declared here}}
107+
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}
108108

109109
protocol CircleEntry : CircleTrivial { }
110110
protocol CircleTrivial : CircleTrivial { } // expected-error {{protocol 'CircleTrivial' refines itself}}

0 commit comments

Comments
 (0)