Skip to content

Commit 2ec6fbe

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 751678d commit 2ec6fbe

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

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

8004-
return ActorIsolation::forNonisolated(false);
8038+
return nominalIsolation;
80058039
}
80068040

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