Skip to content

Commit 926a4cb

Browse files
DougGregorCatfish-Man
authored andcommitted
[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 ab76473 commit 926a4cb

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
@@ -7947,17 +7947,51 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79477947
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
79487948
return ActorIsolation::forNonisolated(false);
79497949

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

7960-
return ActorIsolation::forNonisolated(false);
7994+
return nominalIsolation;
79617995
}
79627996

79637997
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)