Skip to content

[SE-0470] Ensure that one cannot form an isolated conformance when Self: Sendable #80484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/AST/DiagnosticGroups.def
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ GROUP(DynamicCallable, "dynamic-callable-requirements.md")
GROUP(ErrorInFutureSwiftVersion, "error-in-future-swift-version.md")
GROUP(ExistentialAny, "existential-any.md")
GROUP(ExistentialMemberAccess, "existential-member-access-limitations.md")
GROUP(IsolatedConformances, "isolated-conformances.md")
GROUP(MultipleInheritance, "multiple-inheritance.md")
GROUP(MutableGlobalVariable, "mutable-global-variable.md")
GROUP(NominalTypes, "nominal-types.md")
Expand Down
14 changes: 8 additions & 6 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8475,7 +8475,8 @@ ERROR(attr_abi_failable_mismatch,none,
//===----------------------------------------------------------------------===//
// MARK: Isolated conformances
//===----------------------------------------------------------------------===//
ERROR(isolated_conformance_experimental_feature,none,
GROUPED_ERROR(isolated_conformance_experimental_feature,IsolatedConformances,
none,
"isolated conformances require experimental feature "
" 'IsolatedConformances'", ())
NOTE(note_isolate_conformance_to_global_actor,none,
Expand All @@ -8484,15 +8485,16 @@ NOTE(note_isolate_conformance_to_global_actor,none,
NOTE(note_depends_on_isolated_conformance,none,
"conformance depends on %0 conformance of %1 to %kind2",
(ActorIsolation, Type, const ValueDecl *))
ERROR(isolated_conformance_with_sendable,none,
GROUPED_ERROR(isolated_conformance_with_sendable,IsolatedConformances,none,
"%4 conformance of %0 to %1 cannot satisfy conformance "
"requirement for a %select{`Sendable`|`SendableMetatype`}2 type "
"requirement for a %select{'Sendable'|'SendableMetatype'}2 type "
"parameter %3", (Type, DeclName, bool, Type, ActorIsolation))
ERROR(isolated_conformance_with_sendable_simple,none,
GROUPED_ERROR(isolated_conformance_with_sendable_simple,IsolatedConformances,
none,
"%2 conformance of %0 to %1 cannot satisfy "
"conformance requirement for a `Sendable` type parameter ",
"conformance requirement for a 'Sendable' type parameter ",
(Type, DeclName, ActorIsolation))
ERROR(isolated_conformance_wrong_domain,none,
GROUPED_ERROR(isolated_conformance_wrong_domain,IsolatedConformances,none,
"%0 conformance of %1 to %2 cannot be used in %3 context",
(ActorIsolation, Type, DeclName, ActorIsolation))

Expand Down
17 changes: 1 addition & 16 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -739,22 +739,7 @@ class NormalProtocolConformance : public RootProtocolConformance,

void setSourceKindAndImplyingConformance(
ConformanceEntryKind sourceKind,
NormalProtocolConformance *implyingConformance) {
assert(sourceKind != ConformanceEntryKind::Inherited &&
"a normal conformance cannot be inherited");
assert((sourceKind == ConformanceEntryKind::Implied) ==
(bool)implyingConformance &&
"an implied conformance needs something that implies it");
assert(sourceKind != ConformanceEntryKind::PreMacroExpansion &&
"cannot create conformance pre-macro-expansion");
Bits.NormalProtocolConformance.SourceKind = unsigned(sourceKind);
if (auto implying = implyingConformance) {
ImplyingConformance = implying;
PreconcurrencyLoc = implying->getPreconcurrencyLoc();
Bits.NormalProtocolConformance.Options =
implyingConformance->getOptions().toRaw();
}
}
NormalProtocolConformance *implyingConformance);

/// Determine whether this conformance is lazily loaded.
///
Expand Down
29 changes: 29 additions & 0 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ usesDefaultDefinition(AssociatedTypeDecl *requirement) const {
CONFORMANCE_SUBCLASS_DISPATCH(usesDefaultDefinition, (requirement))
}

void NormalProtocolConformance::setSourceKindAndImplyingConformance(
ConformanceEntryKind sourceKind,
NormalProtocolConformance *implyingConformance) {
assert(sourceKind != ConformanceEntryKind::Inherited &&
"a normal conformance cannot be inherited");
assert((sourceKind == ConformanceEntryKind::Implied) ==
(bool)implyingConformance &&
"an implied conformance needs something that implies it");
assert(sourceKind != ConformanceEntryKind::PreMacroExpansion &&
"cannot create conformance pre-macro-expansion");
Bits.NormalProtocolConformance.SourceKind = unsigned(sourceKind);
if (auto implying = implyingConformance) {
ImplyingConformance = implying;
PreconcurrencyLoc = implying->getPreconcurrencyLoc();
Bits.NormalProtocolConformance.Options =
implyingConformance->getOptions().toRaw();
if (getProtocol()->isMarkerProtocol()) {
setExplicitGlobalActorIsolation(nullptr);
} else if (auto globalActorIsolationType =
implyingConformance->getExplicitGlobalActorIsolation()) {
setExplicitGlobalActorIsolation(globalActorIsolationType);
}
}
}

bool ProtocolConformance::isRetroactive() const {
auto extensionModule = getDeclContext()->getParentModule();
auto protocolModule = getProtocol()->getParentModule();
Expand Down Expand Up @@ -494,10 +519,14 @@ void
NormalProtocolConformance::setExplicitGlobalActorIsolation(TypeExpr *typeExpr) {
if (!typeExpr) {
Bits.NormalProtocolConformance.HasExplicitGlobalActor = false;
Bits.NormalProtocolConformance.Options &=
~(unsigned)ProtocolConformanceFlags::GlobalActorIsolated;
return;
}

Bits.NormalProtocolConformance.HasExplicitGlobalActor = true;
Bits.NormalProtocolConformance.Options |=
(unsigned)ProtocolConformanceFlags::GlobalActorIsolated;
ASTContext &ctx = getDeclContext()->getASTContext();
ctx.getGlobalCache().conformanceExplicitGlobalActorIsolation[this] = typeExpr;
}
Expand Down
20 changes: 14 additions & 6 deletions test/Concurrency/isolated_conformance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ struct SMismatchedActors: @MainActor Q {
typealias A = C2
}

protocol PSendable: P, Sendable { }

// expected-error@+2{{type 'PSendableS' does not conform to protocol 'PSendable'}}
// expected-error@+1{{main actor-isolated conformance of 'PSendableS' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter 'Self'}}
struct PSendableS: @MainActor PSendable { // expected-note{{requirement specified as 'Self' : 'P' [with Self = PSendableS]}}
func f() { }
}

// ----------------------------------------------------------------------------
// Use checking of isolated conformances.
// ----------------------------------------------------------------------------
Expand All @@ -108,8 +116,8 @@ struct PSendableMetaWrapper<T: P & SendableMetatype>: P {
@MainActor
func testIsolationConformancesInTypes() {
typealias A1 = PWrapper<C>
typealias A2 = PSendableWrapper<C> // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a `Sendable` type parameter 'T'}}
typealias A3 = PSendableMetaWrapper<C> // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a `SendableMetatype` type parameter 'T'}}
typealias A2 = PSendableWrapper<C> // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter 'T'}}
typealias A3 = PSendableMetaWrapper<C> // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'SendableMetatype' type parameter 'T'}}
}

func acceptP<T: P>(_: T) { }
Expand All @@ -124,20 +132,20 @@ func acceptSendableMetaP<T: SendableMetatype & P>(_: T) { }
func testIsolationConformancesInCall(c: C) {
acceptP(c) // okay

acceptSendableP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a `Sendable` type parameter}}
acceptSendableMetaP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a `Sendable` type parameter}}
acceptSendableP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter}}
acceptSendableMetaP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter}}
}

@MainActor
func testIsolatedConformancesOfActor(a: SomeActor) {
acceptP(a)
acceptSendableMetaP(a) // expected-error{{main actor-isolated conformance of 'SomeActor' to 'P' cannot satisfy conformance requirement for a `Sendable` type parameter}}
acceptSendableMetaP(a) // expected-error{{main actor-isolated conformance of 'SomeActor' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter}}
}

@SomeGlobalActor
func testIsolatedConformancesOfOtherGlobalActor(c: CMismatchedIsolation) {
acceptP(c)
acceptSendableMetaP(c) // expected-error{{global actor 'SomeGlobalActor'-isolated conformance of 'CMismatchedIsolation' to 'P' cannot satisfy conformance requirement for a `Sendable` type parameter}}
acceptSendableMetaP(c) // expected-error{{global actor 'SomeGlobalActor'-isolated conformance of 'CMismatchedIsolation' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter}}
}

func testIsolationConformancesFromOutside(c: C) {
Expand Down
38 changes: 19 additions & 19 deletions userdocs/diagnostics/conformance-isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,26 @@ This code will produce an error similar to:

There are several options for resolving this error, as indicated by the notes:

* If all of the operations used to satisfy the protocol's requirements are on the same global actor (such as the main actor), the conformance itself can be isolated to that global actor. This allows the conformance to be used inside code running on that actor, but it cannot be used concurrency. A conformance can be isolated to a global actor the same way as anything else in the language, e.g.,
```swift
@MainActor
struct MyData: @MainActor P {
func f() { }
}
```
* If all of the operations used to satisfy the protocol's requirements are on the same global actor (such as the main actor), the conformance itself can be isolated to that global actor. This allows the conformance to be used inside code running on that actor, but it cannot be used concurrently. A conformance can be isolated to a global actor the same way as anything else in the language, e.g.,
```swift
@MainActor
struct MyData: @MainActor P {
func f() { }
}
```

* If the conformance needs to be usable anywhere, then each of the operations used to satisfy its requirements must be marked `nonisolated`. This means that they will not have access to any actor-specific operations or state, because these operations can be called concurrently from anywhere. The result would look like this:
```swift
@MainActor
struct MyData: P {
nonisolated func f() { }
}
```
```swift
@MainActor
struct MyData: P {
nonisolated func f() { }
}
```

* If the protocol requirements themselves are meant to always be used from the correct isolation domain (for example, the main actor) but the protocol itself did not describe that requirement, the conformance can be marked with `@preconcurrency`. This approach moves isolation checking into a run-time assertion, which will produce a fatal error if an operation is called without already being on the right actor. A `@preconcurrency` conformance can be written as follows:
```swift
@MainActor
struct MyData: @preconcurrency P {
func f() { }
}
```
```swift
@MainActor
struct MyData: @preconcurrency P {
func f() { }
}
```
40 changes: 40 additions & 0 deletions userdocs/diagnostics/isolated-conformances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Isolated conformances

A protocol conformance can be isolated to a specific global actor, meaning that the conformance can only be used by code running on that actor. Isolated conformances are expressed by specifying the global actor on the conformance itself:

```swift
protocol P {
func f()
}

@MainActor
class MyType: @MainActor P {
/*@MainActor*/ func f() {
// must be called on the main actor
}
}
```

Swift will produce diagnostics if the conformance is directly accessed in code that isn't guaranteed to execute in the same global actor. For example:

```swift
func acceptP<T: P>(_ value: T) { }

/*nonisolated*/ func useIsolatedConformance(myType: MyType) {
acceptP(myType) // error: main actor-isolated conformance of 'MyType' to 'P' cannot be used in nonisolated context
}
```

To address this issue, mark the code as having the same global actor as the conformance it is trying to use. In this case, mark `useIsolatedConformance` as `@MainActor` so that the code is guaranteed to execute on the main actor.

An isolated conformance cannot be used together with a `Sendable` requirement, because doing so would allow the conformance to cross isolation boundaries and be used from outside the global actor. For example:

```swift
func acceptSendableP<T: P & Sendable>(_ value: T) { }

@MainActor func useIsolatedConformanceOnMainActor(myType: MyType) {
acceptSendableP(myType) // error: main-actor-isolated conformance of 'MyType' to 'P' cannot satisfy conformance requirement for 'Sendable' type parameter 'T'
}
```

These errors can be addressed by either making the conformance itself `nonisolated` or making the generic function not require `Sendable`.