Skip to content

Commit 2e34549

Browse files
committed
[SE-0470] Prohibit inference of isolated conformances with nonisolated witnesses
If all of the witnesses to a conformance are nonisolated, then infer that conformance as nonisolated rather than global-actor-isolated. This is only relevant when InferIsolatedConformances is enabled, and prevents that inference to help maintain source compatibility.
1 parent 3f157ab commit 2e34549

File tree

5 files changed

+115
-10
lines changed

5 files changed

+115
-10
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7955,17 +7955,51 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79557955
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
79567956
return ActorIsolation::forNonisolated(false);
79577957

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

7968-
return ActorIsolation::forNonisolated(false);
8002+
return nominalIsolation;
79698003
}
79708004

79718005
namespace {

test/Concurrency/isolated_conformance_default_actor.swift

Lines changed: 2 additions & 2 deletions
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

Lines changed: 7 additions & 0 deletions
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 }}
@@ -49,4 +54,6 @@ nonisolated func testConformancesFromNonisolated() {
4954

5055
// Okay, these are nonisolated conformances.
5156
let _: any Q = CExplicit()
57+
58+
let _: any P = CViaNonisolatedWitness()
5259
}

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 52 additions & 0 deletions
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

Lines changed: 12 additions & 0 deletions
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)