Skip to content

Commit 009f868

Browse files
authored
Merge pull request #81344 from DougGregor/infer-nonisolated-conformances-from-witnesses-6.2
2 parents 20179aa + 2ea1421 commit 009f868

11 files changed

+251
-39
lines changed

include/swift/AST/Evaluator.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,7 @@ class Evaluator {
276276
typename std::enable_if<Request::hasSplitCache>::type* = nullptr>
277277
void cacheNonEmptyOutput(const Request &request,
278278
typename Request::OutputType &&output) {
279-
bool inserted = cache.insert<Request>(request, std::move(output));
280-
assert(inserted && "Request result was already cached");
281-
(void) inserted;
279+
(void)cache.insert<Request>(request, std::move(output));
282280
}
283281

284282
/// Consults the request evaluator's cache for a split-cached request.

include/swift/AST/ProtocolConformance.h

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
131131
/// conformance definition.
132132
Type ConformingType;
133133

134+
friend class ConformanceIsolationRequest;
135+
134136
protected:
135137
// clang-format off
136138
//
@@ -139,9 +141,13 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
139141
union { uint64_t OpaqueBits;
140142

141143
SWIFT_INLINE_BITFIELD_BASE(ProtocolConformance,
144+
1+
142145
bitmax(NumProtocolConformanceKindBits, 8),
143146
/// The kind of protocol conformance.
144-
Kind : bitmax(NumProtocolConformanceKindBits, 8)
147+
Kind : bitmax(NumProtocolConformanceKindBits, 8),
148+
149+
/// Whether the computed actor isolation is nonisolated.
150+
IsComputedNonisolated : 1
145151
);
146152

147153
SWIFT_INLINE_BITFIELD_EMPTY(RootProtocolConformance, ProtocolConformance);
@@ -161,9 +167,6 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
161167
/// this conformance.
162168
IsPreconcurrencyEffectful : 1,
163169

164-
/// Whether the computed actor isolation is nonisolated.
165-
IsComputedNonisolated : 1,
166-
167170
/// Whether there is an explicit global actor specified for this
168171
/// conformance.
169172
HasExplicitGlobalActor : 1,
@@ -198,6 +201,15 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
198201
ProtocolConformance(ProtocolConformanceKind kind, Type conformingType)
199202
: ConformingType(conformingType) {
200203
Bits.ProtocolConformance.Kind = unsigned(kind);
204+
Bits.ProtocolConformance.IsComputedNonisolated = false;
205+
}
206+
207+
bool isComputedNonisolated() const {
208+
return Bits.ProtocolConformance.IsComputedNonisolated;
209+
}
210+
211+
void setComputedNonnisolated(bool value = true) {
212+
Bits.ProtocolConformance.IsComputedNonisolated = value;
201213
}
202214

203215
public:
@@ -591,14 +603,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
591603
// Record the explicitly-specified global actor isolation.
592604
void setExplicitGlobalActorIsolation(TypeExpr *typeExpr);
593605

594-
bool isComputedNonisolated() const {
595-
return Bits.NormalProtocolConformance.IsComputedNonisolated;
596-
}
597-
598-
void setComputedNonnisolated(bool value = true) {
599-
Bits.NormalProtocolConformance.IsComputedNonisolated = value;
600-
}
601-
602606
public:
603607
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
604608
SourceLoc loc, DeclContext *dc,
@@ -622,7 +626,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
622626
Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false;
623627
Bits.NormalProtocolConformance.SourceKind =
624628
unsigned(ConformanceEntryKind::Explicit);
625-
Bits.NormalProtocolConformance.IsComputedNonisolated = false;
626629
Bits.NormalProtocolConformance.HasExplicitGlobalActor = false;
627630
setExplicitGlobalActorIsolation(options.getGlobalActorIsolationType());
628631
}
@@ -714,6 +717,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
714717
return ExplicitSafety::Unspecified;
715718
}
716719

720+
/// Whether this conformance has explicitly-specified global actor isolation.
721+
bool hasExplicitGlobalActorIsolation() const;
722+
717723
/// Determine whether we've lazily computed the associated conformance array
718724
/// already.
719725
bool hasComputedAssociatedConformances() const {

lib/AST/ProtocolConformance.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ TypeExpr *NormalProtocolConformance::getExplicitGlobalActorIsolation() const {
515515
return ctx.getGlobalCache().conformanceExplicitGlobalActorIsolation[this];
516516
}
517517

518+
bool NormalProtocolConformance::hasExplicitGlobalActorIsolation() const {
519+
return Bits.NormalProtocolConformance.HasExplicitGlobalActor;
520+
}
521+
518522
void
519523
NormalProtocolConformance::setExplicitGlobalActorIsolation(TypeExpr *typeExpr) {
520524
if (!typeExpr) {

lib/AST/TypeCheckRequests.cpp

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,31 +1378,25 @@ ConformanceIsolationRequest::getCachedResult() const {
13781378
// everything else, which is nearly every conformance, this request quickly
13791379
// returns "nonisolated" so there is no point in caching it.
13801380
auto conformance = std::get<0>(getStorage());
1381-
auto rootNormal =
1382-
dyn_cast<NormalProtocolConformance>(conformance->getRootConformance());
1383-
if (!rootNormal)
1384-
return ActorIsolation::forNonisolated(false);
13851381

13861382
// Was actor isolation non-isolated?
1387-
if (rootNormal->isComputedNonisolated())
1383+
if (conformance->isComputedNonisolated())
13881384
return ActorIsolation::forNonisolated(false);
13891385

1390-
ASTContext &ctx = rootNormal->getDeclContext()->getASTContext();
1386+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
13911387
return ctx.evaluator.getCachedNonEmptyOutput(*this);
13921388
}
13931389

13941390
void ConformanceIsolationRequest::cacheResult(ActorIsolation result) const {
13951391
auto conformance = std::get<0>(getStorage());
1396-
auto rootNormal =
1397-
cast<NormalProtocolConformance>(conformance->getRootConformance());
13981392

13991393
// Common case: conformance is nonisolated.
14001394
if (result.isNonisolated()) {
1401-
rootNormal->setComputedNonnisolated();
1395+
conformance->setComputedNonnisolated();
14021396
return;
14031397
}
14041398

1405-
ASTContext &ctx = rootNormal->getDeclContext()->getASTContext();
1399+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
14061400
ctx.evaluator.cacheNonEmptyOutput(*this, std::move(result));
14071401
}
14081402

lib/Sema/CodeSynthesis.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1728,12 +1728,52 @@ bool swift::hasLetStoredPropertyWithInitialValue(NominalTypeDecl *nominal) {
17281728
});
17291729
}
17301730

1731+
/// Determine whether a synth
1732+
static bool synthesizedRequirementIsNonIsolated(
1733+
const NormalProtocolConformance *conformance) {
1734+
// @preconcurrency suppresses this.
1735+
if (conformance->isPreconcurrency())
1736+
return false;
1737+
1738+
// Explicit global actor isolation suppresses this.
1739+
if (conformance->hasExplicitGlobalActorIsolation())
1740+
return false;
1741+
1742+
// Explicit nonisolated forces this.
1743+
if (conformance->getOptions()
1744+
.contains(ProtocolConformanceFlags::Nonisolated))
1745+
return true;
1746+
1747+
// When we are inferring conformance isolation, only add nonisolated if
1748+
// either
1749+
// (1) the protocol inherits from SendableMetatype, or
1750+
// (2) the conforming type is nonisolated.
1751+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
1752+
if (!ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances))
1753+
return true;
1754+
1755+
// Check inheritance from SendableMetatype, which implies that the conformance
1756+
// will be nonisolated.
1757+
auto sendableMetatypeProto =
1758+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
1759+
if (sendableMetatypeProto &&
1760+
conformance->getProtocol()->inheritsFrom(sendableMetatypeProto))
1761+
return true;
1762+
1763+
auto nominalType = conformance->getType()->getAnyNominal();
1764+
if (!nominalType)
1765+
return true;
1766+
1767+
return !getActorIsolation(nominalType).isMainActor();
1768+
}
1769+
17311770
bool swift::addNonIsolatedToSynthesized(DerivedConformance &derived,
17321771
ValueDecl *value) {
17331772
if (auto *conformance = derived.Conformance) {
1734-
if (conformance && conformance->isPreconcurrency())
1773+
if (!synthesizedRequirementIsNonIsolated(conformance))
17351774
return false;
17361775
}
1776+
17371777
return addNonIsolatedToSynthesized(derived.Nominal, value);
17381778
}
17391779

lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ addImplicitCodingKeys(NominalTypeDecl *target,
155155
enumDecl->setSynthesized();
156156
enumDecl->setAccess(AccessLevel::Private);
157157

158+
switch (C.LangOpts.DefaultIsolationBehavior) {
159+
case DefaultIsolation::MainActor:
160+
enumDecl->getAttrs().add(NonisolatedAttr::createImplicit(C));
161+
break;
162+
163+
case DefaultIsolation::Nonisolated:
164+
// Nothing to do.
165+
break;
166+
}
167+
158168
// For classes which inherit from something Encodable or Decodable, we
159169
// provide case `super` as the first key (to be used in encoding super).
160170
auto *classDecl = dyn_cast<ClassDecl>(target);

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7934,6 +7934,9 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79347934
if (!rootNormal)
79357935
return ActorIsolation::forNonisolated(false);
79367936

7937+
if (conformance != rootNormal)
7938+
return rootNormal->getIsolation();
7939+
79377940
// If the conformance is explicitly non-isolated, report that.
79387941
if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated))
79397942
return ActorIsolation::forNonisolated(false);
@@ -7965,23 +7968,69 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79657968

79667969
auto dc = rootNormal->getDeclContext();
79677970
ASTContext &ctx = dc->getASTContext();
7971+
auto proto = rootNormal->getProtocol();
79687972

79697973
// If the protocol itself is isolated, don't infer isolation for the
79707974
// conformance.
7971-
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
7975+
if (getActorIsolation(proto).isActorIsolated())
7976+
return ActorIsolation::forNonisolated(false);
7977+
7978+
// SendableMetatype disables isolation inference.
7979+
auto sendableMetatypeProto =
7980+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
7981+
if (sendableMetatypeProto && proto->inheritsFrom(sendableMetatypeProto))
7982+
return ActorIsolation::forNonisolated(false);
7983+
7984+
// @preconcurrency disables isolation inference.
7985+
if (rootNormal->isPreconcurrency())
7986+
return ActorIsolation::forNonisolated(false);
7987+
7988+
7989+
// Isolation inference rules follow. If we aren't inferring isolated conformances,
7990+
// we're done.
7991+
if (!ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances))
79727992
return ActorIsolation::forNonisolated(false);
79737993

7974-
// If we are inferring isolated conformances and the conforming type is
7975-
// isolated to a global actor, use the conforming type's isolation.
79767994
auto nominal = dc->getSelfNominalTypeDecl();
7977-
if (ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances) &&
7978-
nominal) {
7979-
auto nominalIsolation = getActorIsolation(nominal);
7980-
if (nominalIsolation.isGlobalActor())
7981-
return nominalIsolation;
7995+
if (!nominal) {
7996+
return ActorIsolation::forNonisolated(false);
7997+
}
7998+
7999+
// If we are inferring isolated conformances and the conforming type is
8000+
// isolated to a global actor, we may use the conforming type's isolation.
8001+
auto nominalIsolation = getActorIsolation(nominal);
8002+
if (!nominalIsolation.isGlobalActor()) {
8003+
return ActorIsolation::forNonisolated(false);
8004+
}
8005+
8006+
// If all of the value witnesses are nonisolated, then we should not infer
8007+
// global actor isolation.
8008+
bool anyIsolatedWitness = false;
8009+
auto protocol = conformance->getProtocol();
8010+
for (auto requirement : protocol->getMembers()) {
8011+
if (isa<TypeDecl>(requirement))
8012+
continue;
8013+
8014+
auto valueReq = dyn_cast<ValueDecl>(requirement);
8015+
if (!valueReq)
8016+
continue;
8017+
8018+
auto witness = conformance->getWitnessDecl(valueReq);
8019+
if (!witness)
8020+
continue;
8021+
8022+
auto witnessIsolation = getActorIsolation(witness);
8023+
if (witnessIsolation.isActorIsolated()) {
8024+
anyIsolatedWitness = true;
8025+
break;
8026+
}
8027+
}
8028+
8029+
if (!anyIsolatedWitness) {
8030+
return ActorIsolation::forNonisolated(false);
79828031
}
79838032

7984-
return ActorIsolation::forNonisolated(false);
8033+
return nominalIsolation;
79858034
}
79868035

79878036
namespace {

test/Concurrency/isolated_conformance_default_actor.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,41 @@ nonisolated class CNonIsolated: P {
4242
@MainActor func f() { } // expected-note{{main actor-isolated instance method 'f()' cannot satisfy nonisolated requirement}}
4343
}
4444

45+
// Synthesized conformances
46+
struct EquatableStruct: Equatable {
47+
var state: Int = 0
48+
}
49+
50+
struct HashableStruct: Hashable {
51+
var state: Int = 0
52+
}
53+
54+
enum RawRepresentableEnum: Int {
55+
case one
56+
case two
57+
}
58+
59+
class CodableClass: Codable {
60+
var state: Int = 0
61+
}
62+
4563
func acceptSendablePMeta<T: Sendable & P>(_: T.Type) { }
4664
func acceptSendableQMeta<T: Sendable & Q>(_: T.Type) { }
4765

4866
nonisolated func testConformancesFromNonisolated() {
49-
let _: any P = CExplicitMainActor() // expected-error{{main actor-isolated conformance of 'CExplicitMainActor' to 'P' cannot be used in nonisolated context}}
50-
let _: any P = CImplicitMainActor() // expected-error{{main actor-isolated conformance of 'CImplicitMainActor' to 'P' cannot be used in nonisolated context}}
67+
let _: any P = CExplicitMainActor() // okay
68+
let _: any P = CImplicitMainActor() // okay
5169

5270
let _: any P = CNonIsolated()
5371
let _: any P = CImplicitMainActorNonisolatedConformance()
5472

5573
// Okay, these are nonisolated conformances.
5674
let _: any Q = CExplicitMainActor()
5775
let _: any Q = CImplicitMainActor()
76+
77+
// Error, these are main-actor-isolated conformances
78+
let _: any Equatable.Type = EquatableStruct.self // expected-error{{main actor-isolated conformance of 'EquatableStruct' to 'Equatable' cannot be used in nonisolated context}}
79+
let _: any Hashable.Type = HashableStruct.self // expected-error{{main actor-isolated conformance of 'HashableStruct' to 'Hashable' cannot be used in nonisolated context}}
80+
let _: any RawRepresentable.Type = RawRepresentableEnum.self
81+
let _: any Encodable.Type = CodableClass.self // expected-error{{main actor-isolated conformance of 'CodableClass' to 'Encodable' cannot be used in nonisolated context}}
5882
}

test/Concurrency/isolated_conformance_inference.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ extension CExplicit: Q {
3232
func g() { }
3333
}
3434

35+
@SomeGlobalActor
36+
class CViaNonisolatedWitness: P {
37+
nonisolated func f() { } // okay! conformance above is nonisolated via this witness
38+
}
39+
3540
// expected-error@+3{{conformance of 'CNonIsolated' to protocol 'P' crosses into global actor 'SomeGlobalActor'-isolated code and can cause data races}}
3641
// expected-note@+2{{turn data races into runtime errors with '@preconcurrency'}}
3742
// expected-note@+1{{isolate this conformance to the global actor 'SomeGlobalActor' with '@SomeGlobalActor'}}{{33-33=@SomeGlobalActor }}
@@ -42,11 +47,29 @@ nonisolated class CNonIsolated: P {
4247
func acceptSendablePMeta<T: Sendable & P>(_: T.Type) { }
4348
func acceptSendableQMeta<T: Sendable & Q>(_: T.Type) { }
4449

45-
nonisolated func testConformancesFromNonisolated() {
50+
// @preconcurrency suppresses actor isolation inference
51+
struct NotSendable: Equatable, Hashable {
52+
}
53+
54+
@available(*, unavailable)
55+
extension NotSendable: Sendable {}
56+
57+
extension NotSendable : Codable {}
58+
59+
@MainActor
60+
struct TestDerivedCodable : @preconcurrency Codable {
61+
var x: NotSendable
62+
}
63+
64+
nonisolated func testConformancesFromNonisolated(tdc: TestDerivedCodable) {
4665
let _: any P = CExplicit() // expected-error{{global actor 'SomeGlobalActor'-isolated conformance of 'CExplicit' to 'P' cannot be used in nonisolated context}}
4766

4867
let _: any P = CNonIsolated()
4968

5069
// Okay, these are nonisolated conformances.
5170
let _: any Q = CExplicit()
71+
72+
let _: any P = CViaNonisolatedWitness()
73+
74+
let _: any Codable = tdc
5275
}

0 commit comments

Comments
 (0)