Skip to content

Commit aee6a52

Browse files
authored
Merge pull request #81296 from DougGregor/infer-nonisolated-conformances-from-witnesses
[SE-0470] Prohibit inference of isolated conformances with nonisolated witnesses
2 parents 4517a6e + d24aa09 commit aee6a52

8 files changed

+160
-37
lines changed

include/swift/AST/Evaluator.h

+1-3
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

+16-13
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:
@@ -587,14 +599,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
587599
// Record the explicitly-specified global actor isolation.
588600
void setExplicitGlobalActorIsolation(TypeExpr *typeExpr);
589601

590-
bool isComputedNonisolated() const {
591-
return Bits.NormalProtocolConformance.IsComputedNonisolated;
592-
}
593-
594-
void setComputedNonnisolated(bool value = true) {
595-
Bits.NormalProtocolConformance.IsComputedNonisolated = value;
596-
}
597-
598602
public:
599603
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
600604
SourceLoc loc, DeclContext *dc,
@@ -618,7 +622,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
618622
Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false;
619623
Bits.NormalProtocolConformance.SourceKind =
620624
unsigned(ConformanceEntryKind::Explicit);
621-
Bits.NormalProtocolConformance.IsComputedNonisolated = false;
622625
Bits.NormalProtocolConformance.HasExplicitGlobalActor = false;
623626
setExplicitGlobalActorIsolation(options.getGlobalActorIsolationType());
624627
}

lib/AST/TypeCheckRequests.cpp

+4-10
Original file line numberDiff line numberDiff line change
@@ -1373,31 +1373,25 @@ ConformanceIsolationRequest::getCachedResult() const {
13731373
// everything else, which is nearly every conformance, this request quickly
13741374
// returns "nonisolated" so there is no point in caching it.
13751375
auto conformance = std::get<0>(getStorage());
1376-
auto rootNormal =
1377-
dyn_cast<NormalProtocolConformance>(conformance->getRootConformance());
1378-
if (!rootNormal)
1379-
return ActorIsolation::forNonisolated(false);
13801376

13811377
// Was actor isolation non-isolated?
1382-
if (rootNormal->isComputedNonisolated())
1378+
if (conformance->isComputedNonisolated())
13831379
return ActorIsolation::forNonisolated(false);
13841380

1385-
ASTContext &ctx = rootNormal->getDeclContext()->getASTContext();
1381+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
13861382
return ctx.evaluator.getCachedNonEmptyOutput(*this);
13871383
}
13881384

13891385
void ConformanceIsolationRequest::cacheResult(ActorIsolation result) const {
13901386
auto conformance = std::get<0>(getStorage());
1391-
auto rootNormal =
1392-
cast<NormalProtocolConformance>(conformance->getRootConformance());
13931387

13941388
// Common case: conformance is nonisolated.
13951389
if (result.isNonisolated()) {
1396-
rootNormal->setComputedNonnisolated();
1390+
conformance->setComputedNonnisolated();
13971391
return;
13981392
}
13991393

1400-
ASTContext &ctx = rootNormal->getDeclContext()->getASTContext();
1394+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
14011395
ctx.evaluator.cacheNonEmptyOutput(*this, std::move(result));
14021396
}
14031397

lib/Sema/TypeCheckConcurrency.cpp

+49-8
Original file line numberDiff line numberDiff line change
@@ -7910,6 +7910,9 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79107910
if (!rootNormal)
79117911
return ActorIsolation::forNonisolated(false);
79127912

7913+
if (conformance != rootNormal)
7914+
return rootNormal->getIsolation();
7915+
79137916
// If the conformance is explicitly non-isolated, report that.
79147917
if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated))
79157918
return ActorIsolation::forNonisolated(false);
@@ -7947,17 +7950,55 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79477950
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
79487951
return ActorIsolation::forNonisolated(false);
79497952

7950-
// If we are inferring isolated conformances and the conforming type is
7951-
// isolated to a global actor, use the conforming type's isolation.
7953+
// @preconcurrency disables isolation inference.
7954+
if (rootNormal->isPreconcurrency())
7955+
return ActorIsolation::forNonisolated(false);
7956+
7957+
// Isolation inference rules follow. If we aren't inferring isolated conformances,
7958+
// we're done.
7959+
if (!ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances))
7960+
return ActorIsolation::forNonisolated(false);
7961+
79527962
auto nominal = dc->getSelfNominalTypeDecl();
7953-
if (ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances) &&
7954-
nominal) {
7955-
auto nominalIsolation = getActorIsolation(nominal);
7956-
if (nominalIsolation.isGlobalActor())
7957-
return nominalIsolation;
7963+
if (!nominal) {
7964+
return ActorIsolation::forNonisolated(false);
7965+
}
7966+
7967+
// If we are inferring isolated conformances and the conforming type is
7968+
// isolated to a global actor, we may use the conforming type's isolation.
7969+
auto nominalIsolation = getActorIsolation(nominal);
7970+
if (!nominalIsolation.isGlobalActor()) {
7971+
return ActorIsolation::forNonisolated(false);
7972+
}
7973+
7974+
// If all of the value witnesses are nonisolated, then we should not infer
7975+
// global actor isolation.
7976+
bool anyIsolatedWitness = false;
7977+
auto protocol = conformance->getProtocol();
7978+
for (auto requirement : protocol->getMembers()) {
7979+
if (isa<TypeDecl>(requirement))
7980+
continue;
7981+
7982+
auto valueReq = dyn_cast<ValueDecl>(requirement);
7983+
if (!valueReq)
7984+
continue;
7985+
7986+
auto witness = conformance->getWitnessDecl(valueReq);
7987+
if (!witness)
7988+
continue;
7989+
7990+
auto witnessIsolation = getActorIsolation(witness);
7991+
if (witnessIsolation.isActorIsolated()) {
7992+
anyIsolatedWitness = true;
7993+
break;
7994+
}
7995+
}
7996+
7997+
if (!anyIsolatedWitness) {
7998+
return ActorIsolation::forNonisolated(false);
79587999
}
79598000

7960-
return ActorIsolation::forNonisolated(false);
8001+
return nominalIsolation;
79618002
}
79628003

79638004
namespace {

test/Concurrency/isolated_conformance_default_actor.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ func acceptSendablePMeta<T: Sendable & P>(_: T.Type) { }
4646
func acceptSendableQMeta<T: Sendable & Q>(_: T.Type) { }
4747

4848
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}}
49+
let _: any P = CExplicitMainActor() // okay
50+
let _: any P = CImplicitMainActor() // okay
5151

5252
let _: any P = CNonIsolated()
5353
let _: any P = CImplicitMainActorNonisolatedConformance()

test/Concurrency/isolated_conformance_inference.swift

+24-1
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
}

test/Macros/Inputs/syntax_macro_definitions.swift

+52
Original file line numberDiff line numberDiff line change
@@ -2881,6 +2881,58 @@ public struct HangingMacro: PeerMacro {
28812881
}
28822882
}
28832883

2884+
public struct PWithNonisolatedFuncMacro: ExtensionMacro {
2885+
public static var inferNonisolatedConformances: Bool { false }
2886+
2887+
public static func expansion(
2888+
of node: AttributeSyntax,
2889+
attachedTo decl: some DeclGroupSyntax,
2890+
providingExtensionsOf type: some TypeSyntaxProtocol,
2891+
conformingTo protocols: [TypeSyntax],
2892+
in context: some MacroExpansionContext
2893+
) throws -> [ExtensionDeclSyntax] {
2894+
if (protocols.isEmpty) {
2895+
return []
2896+
}
2897+
2898+
let decl: DeclSyntax =
2899+
"""
2900+
extension \(raw: type.trimmedDescription): P {
2901+
nonisolated static func requirement() { }
2902+
}
2903+
"""
2904+
2905+
return [
2906+
decl.cast(ExtensionDeclSyntax.self)
2907+
]
2908+
}
2909+
}
2910+
2911+
public struct NonisolatedPWithNonisolatedFuncMacro: ExtensionMacro {
2912+
public static func expansion(
2913+
of node: AttributeSyntax,
2914+
attachedTo decl: some DeclGroupSyntax,
2915+
providingExtensionsOf type: some TypeSyntaxProtocol,
2916+
conformingTo protocols: [TypeSyntax],
2917+
in context: some MacroExpansionContext
2918+
) throws -> [ExtensionDeclSyntax] {
2919+
if (protocols.isEmpty) {
2920+
return []
2921+
}
2922+
2923+
let decl: DeclSyntax =
2924+
"""
2925+
extension \(raw: type.trimmedDescription): P {
2926+
nonisolated static func requirement() { }
2927+
}
2928+
"""
2929+
2930+
return [
2931+
decl.cast(ExtensionDeclSyntax.self)
2932+
]
2933+
}
2934+
}
2935+
28842936
public struct BigEndianAccessorMacro: AccessorMacro {
28852937
public static func expansion(
28862938
of node: AttributeSyntax,

test/Macros/macro_expand_extensions.swift

+12
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,15 @@ struct HasNestedType {
283283
// extensions of nested types when the outer type has an
284284
// attached macro that can add other nested types.
285285
extension HasNestedType.Inner {}
286+
287+
@attached(extension, conformances: P, names: named(requirement))
288+
macro AddPWithNonisolated() = #externalMacro(module: "MacroDefinition", type: "PWithNonisolatedFuncMacro")
289+
290+
@attached(extension, conformances: P, names: named(requirement))
291+
macro AddNonisolatedPWithNonisolated() = #externalMacro(module: "MacroDefinition", type: "NonisolatedPWithNonisolatedFuncMacro")
292+
293+
@AddNonisolatedPWithNonisolated
294+
struct MakeMeNonisolated { }
295+
296+
@AddPWithNonisolated
297+
struct KeepMeIsolated { }

0 commit comments

Comments
 (0)