Skip to content

Commit b5e4b08

Browse files
committed
Infer ConcurrentValue conformances for structs and enums.
When a struct or enum has only ConcurrentValue-conforming instance data, infer conformance to ConcurrentValue.
1 parent 9962e86 commit b5e4b08

23 files changed

+336
-52
lines changed

include/swift/AST/DeclContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ enum class ConformanceLookupKind : unsigned {
159159
OnlyExplicit,
160160
/// All conformances except for inherited ones.
161161
NonInherited,
162+
/// All conformances except structurally-derived conformances, of which
163+
/// ConcurrentValue is the only one.
164+
NonStructural,
162165
};
163166

164167
/// Describes a diagnostic for a conflict between two protocol

include/swift/AST/TypeCheckRequests.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,6 +2895,26 @@ class SynthesizeMainFunctionRequest
28952895
bool isCached() const { return true; }
28962896
};
28972897

2898+
/// Retrieve the implicit conformance for the given nominal type to
2899+
/// the ConcurrentValue protocol.
2900+
class GetImplicitConcurrentValueRequest :
2901+
public SimpleRequest<GetImplicitConcurrentValueRequest,
2902+
NormalProtocolConformance *(NominalTypeDecl *),
2903+
RequestFlags::Cached> {
2904+
public:
2905+
using SimpleRequest::SimpleRequest;
2906+
2907+
private:
2908+
friend SimpleRequest;
2909+
2910+
NormalProtocolConformance *evaluate(
2911+
Evaluator &evaluator, NominalTypeDecl *nominal) const;
2912+
2913+
public:
2914+
// Caching
2915+
bool isCached() const { return true; }
2916+
};
2917+
28982918
void simple_display(llvm::raw_ostream &out, Type value);
28992919
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
29002920
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,6 @@ SWIFT_REQUEST(TypeChecker, SimpleDidSetRequest,
316316
bool(AccessorDecl *), Cached, NoLocationInfo)
317317
SWIFT_REQUEST(TypeChecker, SynthesizeMainFunctionRequest,
318318
FuncDecl *(Decl *), Cached, NoLocationInfo)
319+
SWIFT_REQUEST(TypeChecker, GetImplicitConcurrentValueRequest,
320+
NormalProtocolConformance *(NominalTypeDecl *),
321+
Cached, NoLocationInfo)

lib/AST/Module.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,17 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
967967

968968
ProtocolConformanceRef ModuleDecl::lookupConformance(Type type,
969969
ProtocolDecl *protocol) {
970+
// If we are recursively checking for implicit conformance of a nominal
971+
// type to ConcurrentValue, fail without evaluating this request. This
972+
// squashes cycles.
973+
if (protocol->isSpecificProtocol(KnownProtocolKind::ConcurrentValue)) {
974+
if (auto nominal = type->getAnyNominal()) {
975+
GetImplicitConcurrentValueRequest request{nominal};
976+
if (getASTContext().evaluator.hasActiveRequest(request))
977+
return ProtocolConformanceRef::forInvalid();
978+
}
979+
}
980+
970981
return evaluateOrDefault(
971982
getASTContext().evaluator,
972983
LookupConformanceInModuleRequest{{this, type, protocol}},
@@ -1035,8 +1046,20 @@ LookupConformanceInModuleRequest::evaluate(
10351046

10361047
// Find the (unspecialized) conformance.
10371048
SmallVector<ProtocolConformance *, 2> conformances;
1038-
if (!nominal->lookupConformance(mod, protocol, conformances))
1039-
return ProtocolConformanceRef::forInvalid();
1049+
if (!nominal->lookupConformance(mod, protocol, conformances)) {
1050+
if (!protocol->isSpecificProtocol(KnownProtocolKind::ConcurrentValue))
1051+
return ProtocolConformanceRef::forInvalid();
1052+
1053+
// Try to infer ConcurrentValue conformance.
1054+
GetImplicitConcurrentValueRequest cvRequest{nominal};
1055+
if (auto conformance = evaluateOrDefault(
1056+
ctx.evaluator, cvRequest, nullptr)) {
1057+
conformances.clear();
1058+
conformances.push_back(conformance);
1059+
} else {
1060+
return ProtocolConformanceRef::forInvalid();
1061+
}
1062+
}
10401063

10411064
// FIXME: Ambiguity resolution.
10421065
auto conformance = conformances.front();

lib/AST/ProtocolConformance.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,41 @@ IterableDeclContext::getLocalProtocols(ConformanceLookupKind lookupKind) const {
13261326
return result;
13271327
}
13281328

1329+
/// Find a synthesized ConcurrentValue conformance in this declaration context,
1330+
/// if there is one.
1331+
static ProtocolConformance *findSynthesizedConcurrentValueConformance(
1332+
const DeclContext *dc) {
1333+
auto nominal = dc->getSelfNominalTypeDecl();
1334+
if (!nominal)
1335+
return nullptr;
1336+
1337+
if (isa<ProtocolDecl>(nominal))
1338+
return nullptr;
1339+
1340+
if (dc->getParentModule() != nominal->getParentModule())
1341+
return nullptr;
1342+
1343+
auto cvProto = nominal->getASTContext().getProtocol(
1344+
KnownProtocolKind::ConcurrentValue);
1345+
if (!cvProto)
1346+
return nullptr;
1347+
1348+
auto conformance = dc->getParentModule()->lookupConformance(
1349+
nominal->getDeclaredInterfaceType(), cvProto);
1350+
if (!conformance || !conformance.isConcrete())
1351+
return nullptr;
1352+
1353+
auto concrete = conformance.getConcrete();
1354+
if (concrete->getDeclContext() != dc)
1355+
return nullptr;
1356+
1357+
auto normal = concrete->getRootNormalConformance();
1358+
if (!normal || normal->getSourceKind() != ConformanceEntryKind::Synthesized)
1359+
return nullptr;
1360+
1361+
return normal;
1362+
}
1363+
13291364
std::vector<ProtocolConformance *>
13301365
LookupAllConformancesInContextRequest::evaluate(
13311366
Evaluator &eval, const IterableDeclContext *IDC) const {
@@ -1394,10 +1429,28 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13941429
}
13951430

13961431
case ConformanceLookupKind::All:
1432+
case ConformanceLookupKind::NonStructural:
13971433
return true;
13981434
}
13991435
});
14001436

1437+
// If we want to add structural conformances, do so now.
1438+
switch (lookupKind) {
1439+
case ConformanceLookupKind::All:
1440+
case ConformanceLookupKind::NonInherited: {
1441+
// Look for a ConcurrentValue conformance globally. If it is synthesized
1442+
// and matches this declaration context, use it.
1443+
auto dc = getAsGenericContext();
1444+
if (auto conformance = findSynthesizedConcurrentValueConformance(dc))
1445+
result.push_back(conformance);
1446+
break;
1447+
}
1448+
1449+
case ConformanceLookupKind::NonStructural:
1450+
case ConformanceLookupKind::OnlyExplicit:
1451+
break;
1452+
}
1453+
14011454
return result;
14021455
}
14031456

lib/Frontend/ModuleInterfaceSupport.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,10 @@ class InheritedProtocolCollector {
590590
DeclAttributes::print(printer, printOptions, attrs);
591591

592592
printer << "extension ";
593-
nominal->getDeclaredType().print(printer, printOptions);
593+
PrintOptions typePrintOptions = printOptions;
594+
typePrintOptions.FullyQualifiedTypes = false;
595+
typePrintOptions.FullyQualifiedTypesIfAmbiguous = false;
596+
nominal->getDeclaredType().print(printer, typePrintOptions);
594597
printer << " : ";
595598

596599
proto->getDeclaredInterfaceType()->print(printer, printOptions);

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,8 @@ getHashableConformance(const Decl *parentDecl) {
942942
ASTContext &C = parentDecl->getASTContext();
943943
const auto IDC = cast<IterableDeclContext>(parentDecl);
944944
auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable);
945-
for (auto conformance: IDC->getLocalConformances()) {
945+
for (auto conformance: IDC->getLocalConformances(
946+
ConformanceLookupKind::NonStructural)) {
946947
if (conformance->getProtocol() == hashableProto) {
947948
return conformance;
948949
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ bool IsAsyncHandlerRequest::evaluate(
178178
// implies @asyncHandler.
179179
{
180180
auto idc = cast<IterableDeclContext>(dc->getAsDecl());
181-
auto conformances = idc->getLocalConformances();
181+
auto conformances = idc->getLocalConformances(
182+
ConformanceLookupKind::NonStructural);
182183

183184
for (auto conformance : conformances) {
184185
auto protocol = conformance->getProtocol();
@@ -2048,7 +2049,8 @@ static Optional<ActorIsolation> getIsolationFromWitnessedRequirements(
20482049

20492050
// Walk through each of the conformances in this context, collecting any
20502051
// requirements that have actor isolation.
2051-
auto conformances = idc->getLocalConformances();
2052+
auto conformances = idc->getLocalConformances(
2053+
ConformanceLookupKind::NonStructural);
20522054
using IsolatedRequirement =
20532055
std::tuple<ProtocolConformance *, ActorIsolation, ValueDecl *>;
20542056
SmallVector<IsolatedRequirement, 2> isolatedRequirements;
@@ -2356,6 +2358,17 @@ static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
23562358
return false;
23572359
}
23582360

2361+
static bool shouldDiagnoseConcurrentValue(ConcurrentValueCheck check) {
2362+
switch (check) {
2363+
case ConcurrentValueCheck::ImpliedByStandardProtocol:
2364+
case ConcurrentValueCheck::Explicit:
2365+
return true;
2366+
2367+
case ConcurrentValueCheck::Implicit:
2368+
return false;
2369+
}
2370+
}
2371+
23592372
/// Check the instance storage of the given nominal type to verify whether
23602373
/// it is comprised only of ConcurrentValue instance storage.
23612374
static bool checkConcurrentValueInstanceStorage(
@@ -2368,6 +2381,9 @@ static bool checkConcurrentValueInstanceStorage(
23682381
auto classDecl = dyn_cast<ClassDecl>(nominal);
23692382
for (auto property : nominal->getStoredProperties()) {
23702383
if (classDecl && property->supportsMutation()) {
2384+
if (!shouldDiagnoseConcurrentValue(check))
2385+
return true;
2386+
23712387
property->diagnose(
23722388
asWarning ? diag::concurrent_value_class_mutable_property_warn
23732389
: diag::concurrent_value_class_mutable_property,
@@ -2379,6 +2395,9 @@ static bool checkConcurrentValueInstanceStorage(
23792395

23802396
auto propertyType = dc->mapTypeIntoContext(property->getInterfaceType());
23812397
if (!isConcurrentValueType(dc, propertyType)) {
2398+
if (!shouldDiagnoseConcurrentValue(check))
2399+
return true;
2400+
23822401
property->diagnose(
23832402
asWarning ? diag::non_concurrent_type_member_warn
23842403
: diag::non_concurrent_type_member,
@@ -2403,6 +2422,9 @@ static bool checkConcurrentValueInstanceStorage(
24032422
auto elementType = dc->mapTypeIntoContext(
24042423
element->getArgumentInterfaceType());
24052424
if (!isConcurrentValueType(dc, elementType)) {
2425+
if (!shouldDiagnoseConcurrentValue(check))
2426+
return true;
2427+
24062428
element->diagnose(
24072429
asWarning ? diag::non_concurrent_type_member_warn
24082430
: diag::non_concurrent_type_member,
@@ -2479,3 +2501,62 @@ bool swift::checkConcurrentValueConformance(
24792501

24802502
return checkConcurrentValueInstanceStorage(nominal, conformanceDC, check);
24812503
}
2504+
2505+
NormalProtocolConformance *GetImplicitConcurrentValueRequest::evaluate(
2506+
Evaluator &evaluator, NominalTypeDecl *nominal) const {
2507+
// Only structs and enums get implicit ConcurrentValue conformances.
2508+
if (!isa<StructDecl>(nominal) && !isa<EnumDecl>(nominal))
2509+
return nullptr;
2510+
2511+
// Check the context in which the conformance occurs.
2512+
if (auto *file = dyn_cast<FileUnit>(nominal->getModuleScopeContext())) {
2513+
switch (file->getKind()) {
2514+
case FileUnitKind::Source:
2515+
// Check what kind of source file we have.
2516+
if (auto sourceFile = nominal->getParentSourceFile()) {
2517+
switch (sourceFile->Kind) {
2518+
case SourceFileKind::Interface:
2519+
// Interfaces have explicitly called-out ConcurrentValue conformances.
2520+
return nullptr;
2521+
2522+
case SourceFileKind::Library:
2523+
case SourceFileKind::Main:
2524+
case SourceFileKind::SIL:
2525+
break;
2526+
}
2527+
}
2528+
break;
2529+
2530+
case FileUnitKind::Builtin:
2531+
case FileUnitKind::SerializedAST:
2532+
case FileUnitKind::Synthesized:
2533+
// Explicitly-handled modules don't infer ConcurrentValue conformances.
2534+
return nullptr;
2535+
2536+
case FileUnitKind::ClangModule:
2537+
case FileUnitKind::DWARFModule:
2538+
// Infer conformances for imported modules.
2539+
break;
2540+
}
2541+
} else {
2542+
return nullptr;
2543+
}
2544+
2545+
// Check the instance storage for ConcurrentValue conformance.
2546+
if (checkConcurrentValueInstanceStorage(
2547+
nominal, nominal, ConcurrentValueCheck::Implicit))
2548+
return nullptr;
2549+
2550+
ASTContext &ctx = nominal->getASTContext();
2551+
auto proto = ctx.getProtocol(KnownProtocolKind::ConcurrentValue);
2552+
if (!proto)
2553+
return nullptr;
2554+
2555+
auto conformance = ctx.getConformance(
2556+
nominal->getDeclaredInterfaceType(), proto, nominal->getLoc(),
2557+
nominal, ProtocolConformanceState::Complete);
2558+
conformance->setSourceKindAndImplyingConformance(
2559+
ConformanceEntryKind::Synthesized, nullptr);
2560+
2561+
return conformance;
2562+
}

lib/Sema/TypeCheckConcurrency.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ enum class ConcurrentValueCheck {
199199
/// ConcurrentValue conformance was implied by one of the standard library
200200
/// protocols that added ConcurrentValue after-the-fact.
201201
ImpliedByStandardProtocol,
202+
203+
/// Implicit conformance to ConcurrentValue for structs and enums.
204+
Implicit,
202205
};
203206

204207
/// Check the correctness of the given ConcurrentValue conformance.

lib/Sema/TypeCheckRequestFunctions.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ static Type inferResultBuilderType(ValueDecl *decl) {
274274
auto addConformanceMatches = [&matches](ValueDecl *lookupDecl) {
275275
DeclContext *dc = lookupDecl->getDeclContext();
276276
auto idc = cast<IterableDeclContext>(dc->getAsDecl());
277-
auto conformances = idc->getLocalConformances();
277+
auto conformances = idc->getLocalConformances(
278+
ConformanceLookupKind::NonStructural);
278279

279280
for (auto conformance : conformances) {
280281
auto protocol = conformance->getProtocol();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
class C1 { }
4+
final class C2: ConcurrentValue { }
5+
6+
struct S1 {
7+
var x: Int
8+
var s: String
9+
var c: C2
10+
}
11+
12+
enum E1 {
13+
case base
14+
indirect case nested(E1)
15+
}
16+
17+
enum E2 {
18+
case s1(S1)
19+
case c2(C2)
20+
}
21+
22+
struct GS1<T> { }
23+
24+
struct GS2<T> {
25+
var storage: T
26+
}
27+
28+
func acceptCV<T: ConcurrentValue>(_: T) { }
29+
// expected-note@-1 3{{where 'T' =}}
30+
31+
func testCV(
32+
c1: C1, c2: C2, s1: S1, e1: E1, e2: E2, gs1: GS1<Int>, gs2: GS2<Int>
33+
) {
34+
acceptCV(c1) // expected-error{{'C1' conform to 'ConcurrentValue'}}
35+
acceptCV(c2)
36+
acceptCV(s1)
37+
acceptCV(e1) // expected-error{{'E1' conform to 'ConcurrentValue'}}
38+
acceptCV(e2)
39+
acceptCV(gs1)
40+
acceptCV(gs2) // expected-error{{'GS2<Int>' conform to 'ConcurrentValue'}}
41+
}

test/Incremental/Verifier/multi-file-private/Inputs/Inner.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ public func blah(foo: Foo) {}
1212
public var defaultFoo: Foo = {
1313
return Inner()
1414
}
15+
// expected-superclass{{main.Inner}}

0 commit comments

Comments
 (0)